Road to No explicit any: Group 8 (part 8) test files (#8496)

## Summary

This PR removes unsafe type assertions ("as unknown as Type") from test
files and improves type safety across the codebase.

### Key Changes

#### Type Safety Improvements
- Removed improper `as unknown as Type` patterns from test files in
Group 8 part 8
- Replaced with proper TypeScript patterns using Pinia store testing
patterns
- Fixed parameter shadowing issue in typeGuardUtil.test.ts (constructor
→ nodeConstructor)
- Fixed stale mock values in useConflictDetection.test.ts using getter
functions
- Refactored useManagerState tests to follow proper Pinia store testing
patterns with createTestingPinia

### Files Changed

Test files (Group 8 part 8 - utils and manager composables):
- src/utils/typeGuardUtil.test.ts - Fixed parameter shadowing
- src/utils/graphTraversalUtil.test.ts - Removed unsafe type assertions
- src/utils/litegraphUtil.test.ts - Improved type handling
- src/workbench/extensions/manager/composables/useManagerState.test.ts -
Complete rewrite using Pinia testing patterns
-
src/workbench/extensions/manager/composables/useConflictDetection.test.ts
- Fixed stale mock values with getters
- src/workbench/extensions/manager/composables/useManagerQueue.test.ts -
Type safety improvements
-
src/workbench/extensions/manager/composables/nodePack/useMissingNodes.test.ts
- Removed unsafe casts
-
src/workbench/extensions/manager/composables/nodePack/usePacksSelection.test.ts
- Type improvements
-
src/workbench/extensions/manager/composables/nodePack/usePacksStatus.test.ts
- Type improvements
- src/workbench/extensions/manager/utils/versionUtil.test.ts - Type
safety fixes

Source files (minor type fixes):
- src/utils/fuseUtil.ts - Type improvements
- src/utils/linkFixer.ts - Type safety fixes
- src/utils/syncUtil.ts - Type improvements
-
src/workbench/extensions/manager/composables/nodePack/useWorkflowPacks.ts
- Type fix
-
src/workbench/extensions/manager/composables/useConflictAcknowledgment.ts
- Type fix

### Testing
- All TypeScript type checking passes (`pnpm typecheck`)
- All affected test files pass (`pnpm test:unit`)
- Linting passes without errors (`pnpm lint`)
- Code formatting applied (`pnpm format`)

Part of the "Road to No Explicit Any" initiative, cleaning up type
casting issues from branch `fix/remove-any-types-part8`.

### Previous PRs in this series:
- Part 2: #7401
- Part 3: #7935
- Part 4: #7970
- Part 5: #8064
- Part 6: #8083
- Part 7: #8092
- Part 8 Group 1: #8253
- Part 8 Group 2: #8258
- Part 8 Group 3: #8304
- Part 8 Group 4: #8314
- Part 8 Group 5: #8329
- Part 8 Group 6: #8344
- Part 8 Group 7: #8459
- Part 8 Group 8: #8496 (this PR)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8496-Road-to-No-explicit-any-Group-8-part-8-test-files-2f86d73d365081f3afdcf8d01fba81e1)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Johnpaul Chiwetelu
2026-01-30 22:25:10 +01:00
committed by GitHub
parent 59c58379fe
commit a64c561a5f
15 changed files with 478 additions and 340 deletions

View File

@@ -75,8 +75,12 @@ export interface FuseSearchable {
postProcessSearchScores: (scores: SearchAuxScore) => SearchAuxScore postProcessSearchScores: (scores: SearchAuxScore) => SearchAuxScore
} }
function isFuseSearchable(item: any): item is FuseSearchable { function isFuseSearchable(item: unknown): item is FuseSearchable {
return 'postProcessSearchScores' in item return (
typeof item === 'object' &&
item !== null &&
'postProcessSearchScores' in item
)
} }
/** /**

View File

@@ -28,6 +28,7 @@ import {
triggerCallbackOnAllNodes, triggerCallbackOnAllNodes,
visitGraphNodes visitGraphNodes
} from '@/utils/graphTraversalUtil' } from '@/utils/graphTraversalUtil'
import { createMockLGraphNode } from './__tests__/litegraphTestUtils'
// Mock node factory // Mock node factory
function createMockNode( function createMockNode(
@@ -39,13 +40,13 @@ function createMockNode(
graph?: LGraph graph?: LGraph
} = {} } = {}
): LGraphNode { ): LGraphNode {
const node = { const node = createMockLGraphNode({
id, id,
isSubgraphNode: options.isSubgraph ? () => true : undefined, isSubgraphNode: options.isSubgraph ? () => true : undefined,
subgraph: options.subgraph, subgraph: options.subgraph,
onExecutionStart: options.callback, onExecutionStart: options.callback,
graph: options.graph graph: options.graph
} as unknown as LGraphNode }) satisfies Partial<LGraphNode> as LGraphNode
options.graph?.nodes?.push(node) options.graph?.nodes?.push(node)
return node return node
} }
@@ -58,7 +59,7 @@ function createMockGraph(nodes: LGraphNode[]): LGraph {
isRootGraph: true, isRootGraph: true,
getNodeById: (id: string | number) => getNodeById: (id: string | number) =>
nodes.find((n) => String(n.id) === String(id)) || null nodes.find((n) => String(n.id) === String(id)) || null
} as unknown as LGraph } satisfies Partial<LGraph> as LGraph
} }
// Mock subgraph factory // Mock subgraph factory
@@ -75,7 +76,7 @@ function createMockSubgraph(
rootGraph, rootGraph,
getNodeById: (nodeId: string | number) => getNodeById: (nodeId: string | number) =>
nodes.find((n) => String(n.id) === String(nodeId)) || null nodes.find((n) => String(n.id) === String(nodeId)) || null
} as unknown as Subgraph } satisfies Partial<Subgraph> as Subgraph
return graph return graph
} }
@@ -96,8 +97,8 @@ describe('graphTraversalUtil', () => {
it('should return null for invalid input', () => { it('should return null for invalid input', () => {
expect(parseExecutionId('')).toBeNull() expect(parseExecutionId('')).toBeNull()
expect(parseExecutionId(null as any)).toBeNull() expect(parseExecutionId(null!)).toBeNull()
expect(parseExecutionId(undefined as any)).toBeNull() expect(parseExecutionId(undefined!)).toBeNull()
}) })
}) })
@@ -415,7 +416,7 @@ describe('graphTraversalUtil', () => {
// Add a title property to each node // Add a title property to each node
forEachNode(graph, (node) => { forEachNode(graph, (node) => {
;(node as any).title = `Node ${node.id}` node.title = `Node ${node.id}`
}) })
expect(nodes[0]).toHaveProperty('title', 'Node 1') expect(nodes[0]).toHaveProperty('title', 'Node 1')
@@ -653,7 +654,7 @@ describe('graphTraversalUtil', () => {
it('should return root graph from subgraph', () => { it('should return root graph from subgraph', () => {
const rootGraph = createMockGraph([]) const rootGraph = createMockGraph([])
const subgraph = createMockSubgraph('sub-uuid', []) const subgraph = createMockSubgraph('sub-uuid', [])
;(subgraph as any).rootGraph = rootGraph ;(subgraph as Subgraph & { rootGraph: LGraph }).rootGraph = rootGraph
expect(getRootGraph(subgraph)).toBe(rootGraph) expect(getRootGraph(subgraph)).toBe(rootGraph)
}) })
@@ -662,9 +663,10 @@ describe('graphTraversalUtil', () => {
const rootGraph = createMockGraph([]) const rootGraph = createMockGraph([])
const midSubgraph = createMockSubgraph('mid-uuid', []) const midSubgraph = createMockSubgraph('mid-uuid', [])
const deepSubgraph = createMockSubgraph('deep-uuid', []) const deepSubgraph = createMockSubgraph('deep-uuid', [])
;(midSubgraph as Subgraph & { rootGraph: LGraph }).rootGraph = rootGraph
;(midSubgraph as any).rootGraph = rootGraph ;(
;(deepSubgraph as any).rootGraph = midSubgraph deepSubgraph as Subgraph & { rootGraph: LGraph | Subgraph }
).rootGraph = midSubgraph
expect(getRootGraph(deepSubgraph)).toBe(rootGraph) expect(getRootGraph(deepSubgraph)).toBe(rootGraph)
}) })
@@ -726,7 +728,7 @@ describe('graphTraversalUtil', () => {
const graph = createMockGraph(nodes) const graph = createMockGraph(nodes)
forEachSubgraphNode(graph, subgraphId, (node) => { forEachSubgraphNode(graph, subgraphId, (node) => {
;(node as any).title = 'Updated Title' node.title = 'Updated Title'
}) })
expect(nodes[0]).toHaveProperty('title', 'Updated Title') expect(nodes[0]).toHaveProperty('title', 'Updated Title')

View File

@@ -24,6 +24,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import type { INodeOutputSlot } from '@/lib/litegraph/src/interfaces'
import type { NodeId } from '@/lib/litegraph/src/LGraphNode' import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
import type { SerialisedLLinkArray } from '@/lib/litegraph/src/LLink' import type { SerialisedLLinkArray } from '@/lib/litegraph/src/LLink'
import type { LGraph, LGraphNode, LLink } from '@/lib/litegraph/src/litegraph' import type { LGraph, LGraphNode, LLink } from '@/lib/litegraph/src/litegraph'
@@ -79,12 +80,12 @@ export function fixBadLinks(
options: { options: {
fix?: boolean fix?: boolean
silent?: boolean silent?: boolean
logger?: { log: (...args: any[]) => void } logger?: { log: (...args: unknown[]) => void }
} = {} } = {}
): BadLinksData { ): BadLinksData {
const { fix = false, silent = false, logger: _logger = console } = options const { fix = false, silent = false, logger: _logger = console } = options
const logger = { const logger = {
log: (...args: any[]) => { log: (...args: unknown[]) => {
if (!silent) { if (!silent) {
_logger.log(...args) _logger.log(...args)
} }
@@ -166,7 +167,9 @@ export function fixBadLinks(
patchedNode['outputs']![slot]!['links'].push(linkId) patchedNode['outputs']![slot]!['links'].push(linkId)
if (fix) { if (fix) {
node.outputs = node.outputs || [] node.outputs = node.outputs || []
node.outputs[slot] = node.outputs[slot] || ({} as any) node.outputs[slot] =
node.outputs[slot] ||
({} satisfies Partial<INodeOutputSlot> as INodeOutputSlot)
node.outputs[slot]!.links = node.outputs[slot]!.links || [] node.outputs[slot]!.links = node.outputs[slot]!.links || []
node.outputs[slot]!.links!.push(linkId) node.outputs[slot]!.links!.push(linkId)
} }
@@ -428,7 +431,7 @@ export function fixBadLinks(
(l) => (l) =>
l && l &&
(l[0] === data.deletedLinks[i] || (l[0] === data.deletedLinks[i] ||
(l as any).id === data.deletedLinks[i]) ('id' in l && l.id === data.deletedLinks[i]))
) )
if (idx === -1) { if (idx === -1) {
logger.log(`INDEX NOT FOUND for #${data.deletedLinks[i]}`) logger.log(`INDEX NOT FOUND for #${data.deletedLinks[i]}`)

View File

@@ -26,10 +26,10 @@ describe('migrateWidgetsValues', () => {
} }
} }
const widgets: IWidget[] = [ const widgets = [
{ name: 'normalInput', type: 'number' }, { name: 'normalInput', type: 'number' },
{ name: 'anotherNormal', type: 'number' } { name: 'anotherNormal', type: 'number' }
] as unknown as IWidget[] ] as Partial<IWidget>[] as IWidget[]
const widgetValues = [42, 'dummy value', 3.14] const widgetValues = [42, 'dummy value', 3.14]
@@ -56,7 +56,7 @@ describe('migrateWidgetsValues', () => {
it('should handle empty widgets and values', () => { it('should handle empty widgets and values', () => {
const inputDefs: Record<string, InputSpec> = {} const inputDefs: Record<string, InputSpec> = {}
const widgets: IWidget[] = [] const widgets: IWidget[] = []
const widgetValues: any[] = [] const widgetValues: unknown[] = []
const result = migrateWidgetsValues(inputDefs, widgets, widgetValues) const result = migrateWidgetsValues(inputDefs, widgets, widgetValues)
expect(result).toEqual([]) expect(result).toEqual([])
@@ -79,10 +79,10 @@ describe('migrateWidgetsValues', () => {
} }
} }
const widgets: IWidget[] = [ const widgets = [
{ name: 'first', type: 'number' }, { name: 'first', type: 'number' },
{ name: 'last', type: 'number' } { name: 'last', type: 'number' }
] as unknown as IWidget[] ] as Partial<IWidget>[] as IWidget[]
const widgetValues = ['first value', 'dummy', 'last value'] const widgetValues = ['first value', 'dummy', 'last value']
@@ -93,7 +93,8 @@ describe('migrateWidgetsValues', () => {
describe('compressWidgetInputSlots', () => { describe('compressWidgetInputSlots', () => {
it('should remove unconnected widget input slots', () => { it('should remove unconnected widget input slots', () => {
const graph: ISerialisedGraph = { // Using partial mock - only including properties needed for test
const graph = {
nodes: [ nodes: [
{ {
id: 1, id: 1,
@@ -112,7 +113,7 @@ describe('compressWidgetInputSlots', () => {
} }
], ],
links: [[2, 1, 0, 1, 0, 'INT']] links: [[2, 1, 0, 1, 0, 'INT']]
} as unknown as ISerialisedGraph } as Partial<ISerialisedGraph> as ISerialisedGraph
compressWidgetInputSlots(graph) compressWidgetInputSlots(graph)
@@ -122,7 +123,7 @@ describe('compressWidgetInputSlots', () => {
}) })
it('should update link target slots correctly', () => { it('should update link target slots correctly', () => {
const graph: ISerialisedGraph = { const graph = {
nodes: [ nodes: [
{ {
id: 1, id: 1,
@@ -144,7 +145,7 @@ describe('compressWidgetInputSlots', () => {
[2, 1, 0, 1, 1, 'INT'], [2, 1, 0, 1, 1, 'INT'],
[3, 1, 0, 1, 2, 'INT'] [3, 1, 0, 1, 2, 'INT']
] ]
} as unknown as ISerialisedGraph } as Partial<ISerialisedGraph> as ISerialisedGraph
compressWidgetInputSlots(graph) compressWidgetInputSlots(graph)
@@ -160,10 +161,11 @@ describe('compressWidgetInputSlots', () => {
}) })
it('should handle graphs with no nodes gracefully', () => { it('should handle graphs with no nodes gracefully', () => {
const graph: ISerialisedGraph = { // Using partial mock - only including properties needed for test
const graph = {
nodes: [], nodes: [],
links: [] links: []
} as unknown as ISerialisedGraph } as Partial<ISerialisedGraph> as ISerialisedGraph
compressWidgetInputSlots(graph) compressWidgetInputSlots(graph)

View File

@@ -1,4 +1,5 @@
import { api } from '@/scripts/api' import { api } from '@/scripts/api'
import type { UserDataFullInfo } from '@/schemas/apiSchema'
/** /**
* Sync entities from the API to the entityByPath map. * Sync entities from the API to the entityByPath map.
@@ -11,8 +12,8 @@ import { api } from '@/scripts/api'
export async function syncEntities<T>( export async function syncEntities<T>(
dir: string, dir: string,
entityByPath: Record<string, T>, entityByPath: Record<string, T>,
createEntity: (file: any) => T, createEntity: (file: UserDataFullInfo & { path: string }) => T,
updateEntity: (entity: T, file: any) => void, updateEntity: (entity: T, file: UserDataFullInfo & { path: string }) => void,
exclude: (file: T) => boolean = () => false exclude: (file: T) => boolean = () => false
) { ) {
const files = (await api.listUserDataFullInfo(dir)).map((file) => ({ const files = (await api.listUserDataFullInfo(dir)).map((file) => ({

View File

@@ -1,43 +1,42 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { isSubgraphIoNode } from '@/utils/typeGuardUtil' import { isSubgraphIoNode } from '@/utils/typeGuardUtil'
type NodeConstructor = { comfyClass?: string }
function createMockNode(nodeConstructor?: NodeConstructor): LGraphNode {
return { constructor: nodeConstructor } as Partial<LGraphNode> as LGraphNode
}
describe('typeGuardUtil', () => { describe('typeGuardUtil', () => {
describe('isSubgraphIoNode', () => { describe('isSubgraphIoNode', () => {
it('should identify SubgraphInputNode as IO node', () => { it('should identify SubgraphInputNode as IO node', () => {
const node = { const node = createMockNode({ comfyClass: 'SubgraphInputNode' })
constructor: { comfyClass: 'SubgraphInputNode' }
} as any
expect(isSubgraphIoNode(node)).toBe(true) expect(isSubgraphIoNode(node)).toBe(true)
}) })
it('should identify SubgraphOutputNode as IO node', () => { it('should identify SubgraphOutputNode as IO node', () => {
const node = { const node = createMockNode({ comfyClass: 'SubgraphOutputNode' })
constructor: { comfyClass: 'SubgraphOutputNode' }
} as any
expect(isSubgraphIoNode(node)).toBe(true) expect(isSubgraphIoNode(node)).toBe(true)
}) })
it('should not identify regular nodes as IO nodes', () => { it('should not identify regular nodes as IO nodes', () => {
const node = { const node = createMockNode({ comfyClass: 'CLIPTextEncode' })
constructor: { comfyClass: 'CLIPTextEncode' }
} as any
expect(isSubgraphIoNode(node)).toBe(false) expect(isSubgraphIoNode(node)).toBe(false)
}) })
it('should handle nodes without constructor', () => { it('should handle nodes without constructor', () => {
const node = {} as any const node = createMockNode(undefined)
expect(isSubgraphIoNode(node)).toBe(false) expect(isSubgraphIoNode(node)).toBe(false)
}) })
it('should handle nodes without comfyClass', () => { it('should handle nodes without comfyClass', () => {
const node = { const node = createMockNode({})
constructor: {}
} as any
expect(isSubgraphIoNode(node)).toBe(false) expect(isSubgraphIoNode(node)).toBe(false)
}) })

View File

@@ -2,17 +2,22 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick, ref } from 'vue' import { nextTick, ref } from 'vue'
import type { LGraphNode, LGraph } from '@/lib/litegraph/src/litegraph' import type { LGraphNode, LGraph } from '@/lib/litegraph/src/litegraph'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import { useNodeDefStore } from '@/stores/nodeDefStore' import { useNodeDefStore } from '@/stores/nodeDefStore'
import { collectAllNodes } from '@/utils/graphTraversalUtil' import { collectAllNodes } from '@/utils/graphTraversalUtil'
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes' import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
import { useWorkflowPacks } from '@/workbench/extensions/manager/composables/nodePack/useWorkflowPacks' import { useWorkflowPacks } from '@/workbench/extensions/manager/composables/nodePack/useWorkflowPacks'
import type { WorkflowPack } from '@/workbench/extensions/manager/composables/nodePack/useWorkflowPacks'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore' import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils'
vi.mock('@vueuse/core', async () => { vi.mock('@vueuse/core', async () => {
const actual = await vi.importActual('@vueuse/core') const actual = await vi.importActual('@vueuse/core')
return { return {
...actual, ...actual,
createSharedComposable: <Fn extends (...args: any[]) => any>(fn: Fn) => fn createSharedComposable: <Fn extends (...args: unknown[]) => unknown>(
fn: Fn
) => fn
} }
}) })
@@ -81,11 +86,11 @@ describe('useMissingNodes', () => {
// Default setup: pack-3 is installed, others are not // Default setup: pack-3 is installed, others are not
mockIsPackInstalled.mockImplementation((id: string) => id === 'pack-3') mockIsPackInstalled.mockImplementation((id: string) => id === 'pack-3')
// @ts-expect-error - Mocking partial ComfyManagerStore for testing.
// We only need isPackInstalled method for these tests.
mockUseComfyManagerStore.mockReturnValue({ mockUseComfyManagerStore.mockReturnValue({
isPackInstalled: mockIsPackInstalled isPackInstalled: mockIsPackInstalled
}) } as Partial<ReturnType<typeof useComfyManagerStore>> as ReturnType<
typeof useComfyManagerStore
>)
mockUseWorkflowPacks.mockReturnValue({ mockUseWorkflowPacks.mockReturnValue({
workflowPacks: ref([]), workflowPacks: ref([]),
@@ -97,11 +102,11 @@ describe('useMissingNodes', () => {
}) })
// Reset node def store mock // Reset node def store mock
// @ts-expect-error - Mocking partial NodeDefStore for testing.
// We only need nodeDefsByName for these tests.
mockUseNodeDefStore.mockReturnValue({ mockUseNodeDefStore.mockReturnValue({
nodeDefsByName: {} nodeDefsByName: {}
}) } as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
typeof useNodeDefStore
>)
// Reset app.rootGraph.nodes // Reset app.rootGraph.nodes
mockApp.rootGraph = { nodes: [] } mockApp.rootGraph = { nodes: [] }
@@ -249,7 +254,7 @@ describe('useMissingNodes', () => {
describe('reactivity', () => { describe('reactivity', () => {
it('updates when workflow packs change', async () => { it('updates when workflow packs change', async () => {
const workflowPacksRef = ref([]) const workflowPacksRef = ref<WorkflowPack[]>([])
mockUseWorkflowPacks.mockReturnValue({ mockUseWorkflowPacks.mockReturnValue({
workflowPacks: workflowPacksRef, workflowPacks: workflowPacksRef,
isLoading: ref(false), isLoading: ref(false),
@@ -265,8 +270,8 @@ describe('useMissingNodes', () => {
expect(missingNodePacks.value).toEqual([]) expect(missingNodePacks.value).toEqual([])
// Update workflow packs // Update workflow packs
// @ts-expect-error - mockWorkflowPacks is a simplified version without full WorkflowPack interface.
workflowPacksRef.value = mockWorkflowPacks workflowPacksRef.value = mockWorkflowPacks as unknown as WorkflowPack[]
await nextTick() await nextTick()
// Should update missing packs (2 missing since pack-3 is installed) // Should update missing packs (2 missing since pack-3 is installed)
@@ -302,7 +307,7 @@ describe('useMissingNodes', () => {
describe('missing core nodes detection', () => { describe('missing core nodes detection', () => {
const createMockNode = (type: string, packId?: string, version?: string) => const createMockNode = (type: string, packId?: string, version?: string) =>
({ createMockLGraphNode({
type, type,
properties: { cnr_id: packId, ver: version }, properties: { cnr_id: packId, ver: version },
id: 1, id: 1,
@@ -314,7 +319,7 @@ describe('useMissingNodes', () => {
mode: 0, mode: 0,
inputs: [], inputs: [],
outputs: [] outputs: []
}) as unknown as LGraphNode })
it('identifies missing core nodes not in nodeDefStore', () => { it('identifies missing core nodes not in nodeDefStore', () => {
const coreNode1 = createMockNode('CoreNode1', 'comfy-core', '1.2.0') const coreNode1 = createMockNode('CoreNode1', 'comfy-core', '1.2.0')
@@ -323,13 +328,16 @@ describe('useMissingNodes', () => {
// Mock collectAllNodes to return only the filtered nodes (missing core nodes) // Mock collectAllNodes to return only the filtered nodes (missing core nodes)
mockCollectAllNodes.mockReturnValue([coreNode1, coreNode2]) mockCollectAllNodes.mockReturnValue([coreNode1, coreNode2])
const namedNode = {
name: 'RegisteredNode'
} as Partial<ComfyNodeDefImpl> as ComfyNodeDefImpl
mockUseNodeDefStore.mockReturnValue({ mockUseNodeDefStore.mockReturnValue({
nodeDefsByName: { nodeDefsByName: {
// @ts-expect-error - Creating minimal mock of ComfyNodeDefImpl for testing. RegisteredNode: namedNode
// Only including required properties for our test assertions.
RegisteredNode: { name: 'RegisteredNode' }
} }
}) } as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
typeof useNodeDefStore
>)
const { missingCoreNodes } = useMissingNodes() const { missingCoreNodes } = useMissingNodes()
@@ -347,10 +355,11 @@ describe('useMissingNodes', () => {
// Mock collectAllNodes to return these nodes // Mock collectAllNodes to return these nodes
mockCollectAllNodes.mockReturnValue([node120, node130, nodeNoVer]) mockCollectAllNodes.mockReturnValue([node120, node130, nodeNoVer])
// @ts-expect-error - Mocking partial NodeDefStore for testing.
mockUseNodeDefStore.mockReturnValue({ mockUseNodeDefStore.mockReturnValue({
nodeDefsByName: {} nodeDefsByName: {}
}) } as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
typeof useNodeDefStore
>)
const { missingCoreNodes } = useMissingNodes() const { missingCoreNodes } = useMissingNodes()
@@ -366,10 +375,11 @@ describe('useMissingNodes', () => {
// Mock collectAllNodes to return only the filtered nodes (core nodes only) // Mock collectAllNodes to return only the filtered nodes (core nodes only)
mockCollectAllNodes.mockReturnValue([coreNode]) mockCollectAllNodes.mockReturnValue([coreNode])
// @ts-expect-error - Mocking partial NodeDefStore for testing.
mockUseNodeDefStore.mockReturnValue({ mockUseNodeDefStore.mockReturnValue({
nodeDefsByName: {} nodeDefsByName: {}
}) } as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
typeof useNodeDefStore
>)
const { missingCoreNodes } = useMissingNodes() const { missingCoreNodes } = useMissingNodes()
@@ -384,13 +394,16 @@ describe('useMissingNodes', () => {
mockUseNodeDefStore.mockReturnValue({ mockUseNodeDefStore.mockReturnValue({
nodeDefsByName: { nodeDefsByName: {
// @ts-expect-error - Creating minimal mock of ComfyNodeDefImpl for testing. RegisteredNode1: {
// Only including required properties for our test assertions. name: 'RegisteredNode1'
RegisteredNode1: { name: 'RegisteredNode1' }, } as Partial<ComfyNodeDefImpl> as ComfyNodeDefImpl,
// @ts-expect-error - Creating minimal mock of ComfyNodeDefImpl for testing. RegisteredNode2: {
RegisteredNode2: { name: 'RegisteredNode2' } name: 'RegisteredNode2'
} as Partial<ComfyNodeDefImpl> as ComfyNodeDefImpl
} }
}) } as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
typeof useNodeDefStore
>)
const { missingCoreNodes } = useMissingNodes() const { missingCoreNodes } = useMissingNodes()
@@ -404,9 +417,7 @@ describe('useMissingNodes', () => {
packId?: string, packId?: string,
version?: string version?: string
): LGraphNode => ): LGraphNode =>
// @ts-expect-error - Creating a partial mock of LGraphNode for testing. createMockLGraphNode({
// We only need specific properties for our tests, not the full LGraphNode interface.
({
type, type,
properties: { cnr_id: packId, ver: version }, properties: { cnr_id: packId, ver: version },
id: 1, id: 1,
@@ -441,10 +452,11 @@ describe('useMissingNodes', () => {
]) ])
// Mock none of the nodes as registered // Mock none of the nodes as registered
// @ts-expect-error - Mocking partial NodeDefStore for testing.
mockUseNodeDefStore.mockReturnValue({ mockUseNodeDefStore.mockReturnValue({
nodeDefsByName: {} nodeDefsByName: {}
}) } as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
typeof useNodeDefStore
>)
const { missingCoreNodes } = useMissingNodes() const { missingCoreNodes } = useMissingNodes()
@@ -482,10 +494,13 @@ describe('useMissingNodes', () => {
mockUseNodeDefStore.mockReturnValue({ mockUseNodeDefStore.mockReturnValue({
nodeDefsByName: { nodeDefsByName: {
// @ts-expect-error - Creating minimal mock of ComfyNodeDefImpl for testing. RegisteredCore: {
RegisteredCore: { name: 'RegisteredCore' } name: 'RegisteredCore'
} as Partial<ComfyNodeDefImpl> as ComfyNodeDefImpl
} }
}) } as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
typeof useNodeDefStore
>)
let capturedFilterFunction: ((node: LGraphNode) => boolean) | undefined let capturedFilterFunction: ((node: LGraphNode) => boolean) | undefined
@@ -561,12 +576,12 @@ describe('useMissingNodes', () => {
nodes: [subgraphMissingNode, subgraphRegisteredNode] nodes: [subgraphMissingNode, subgraphRegisteredNode]
} }
const mockSubgraphNode = { const mockSubgraphNode = createMockLGraphNode({
isSubgraphNode: () => true, isSubgraphNode: () => true,
subgraph: mockSubgraph, subgraph: mockSubgraph,
type: 'SubgraphContainer', type: 'SubgraphContainer',
properties: { cnr_id: 'custom-pack' } properties: { cnr_id: 'custom-pack' }
} as unknown as LGraphNode })
const mockMainGraph = { const mockMainGraph = {
nodes: [mainMissingNode, mockSubgraphNode] nodes: [mainMissingNode, mockSubgraphNode]
@@ -576,10 +591,13 @@ describe('useMissingNodes', () => {
mockUseNodeDefStore.mockReturnValue({ mockUseNodeDefStore.mockReturnValue({
nodeDefsByName: { nodeDefsByName: {
// @ts-expect-error - Creating minimal mock of ComfyNodeDefImpl for testing. SubgraphRegistered: {
SubgraphRegistered: { name: 'SubgraphRegistered' } name: 'SubgraphRegistered'
} as Partial<ComfyNodeDefImpl> as ComfyNodeDefImpl
} }
}) } as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
typeof useNodeDefStore
>)
const { missingCoreNodes } = useMissingNodes() const { missingCoreNodes } = useMissingNodes()

View File

@@ -324,7 +324,7 @@ describe('usePacksSelection', () => {
describe('edge cases', () => { describe('edge cases', () => {
it('should handle packs with undefined ids', () => { it('should handle packs with undefined ids', () => {
const nodePacks = ref<NodePack[]>([ const nodePacks = ref<NodePack[]>([
{ ...createMockPack('pack1'), id: undefined as any }, { ...createMockPack('pack1'), id: undefined },
createMockPack('pack2') createMockPack('pack2')
]) ])

View File

@@ -108,7 +108,7 @@ describe('usePacksStatus', () => {
it('should handle packs without ids', () => { it('should handle packs without ids', () => {
const nodePacks = ref<NodePack[]>([ const nodePacks = ref<NodePack[]>([
{ ...createMockPack('pack1'), id: undefined as any }, { ...createMockPack('pack1'), id: undefined },
createMockPack('pack2') createMockPack('pack2')
]) ])

View File

@@ -11,7 +11,7 @@ import type { components } from '@/types/comfyRegistryTypes'
import { mapAllNodes } from '@/utils/graphTraversalUtil' import { mapAllNodes } from '@/utils/graphTraversalUtil'
import { useNodePacks } from '@/workbench/extensions/manager/composables/nodePack/useNodePacks' import { useNodePacks } from '@/workbench/extensions/manager/composables/nodePack/useNodePacks'
type WorkflowPack = { export type WorkflowPack = {
id: id:
| ComfyWorkflowJSON['nodes'][number]['properties']['cnr_id'] | ComfyWorkflowJSON['nodes'][number]['properties']['cnr_id']
| ComfyWorkflowJSON['nodes'][number]['properties']['aux_id'] | ComfyWorkflowJSON['nodes'][number]['properties']['aux_id']

View File

@@ -15,7 +15,7 @@ const STORAGE_KEYS = {
/** /**
* Interface for conflict acknowledgment state * Interface for conflict acknowledgment state
*/ */
interface ConflictAcknowledgmentState { export interface ConflictAcknowledgmentState {
modal_dismissed: boolean modal_dismissed: boolean
red_dot_dismissed: boolean red_dot_dismissed: boolean
warning_banner_dismissed: boolean warning_banner_dismissed: boolean

View File

@@ -8,6 +8,7 @@ import { useSystemStatsStore } from '@/stores/systemStatsStore'
import type { components } from '@/types/comfyRegistryTypes' import type { components } from '@/types/comfyRegistryTypes'
import { useInstalledPacks } from '@/workbench/extensions/manager/composables/nodePack/useInstalledPacks' import { useInstalledPacks } from '@/workbench/extensions/manager/composables/nodePack/useInstalledPacks'
import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment' import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
import type { ConflictAcknowledgmentState } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection' import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
import { useComfyManagerService } from '@/workbench/extensions/manager/services/comfyManagerService' import { useComfyManagerService } from '@/workbench/extensions/manager/services/comfyManagerService'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore' import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
@@ -121,13 +122,17 @@ describe('useConflictDetection', () => {
getImportFailInfoBulk: vi.fn(), getImportFailInfoBulk: vi.fn(),
isLoading: ref(false), isLoading: ref(false),
error: ref<string | null>(null) error: ref<string | null>(null)
} as unknown as ReturnType<typeof useComfyManagerService> } as Partial<ReturnType<typeof useComfyManagerService>> as ReturnType<
typeof useComfyManagerService
>
const mockRegistryService = { const mockRegistryService = {
getBulkNodeVersions: vi.fn(), getBulkNodeVersions: vi.fn(),
isLoading: ref(false), isLoading: ref(false),
error: ref<string | null>(null) error: ref<string | null>(null)
} as unknown as ReturnType<typeof useComfyRegistryService> } as Partial<ReturnType<typeof useComfyRegistryService>> as ReturnType<
typeof useComfyRegistryService
>
// Create a ref that can be modified in tests // Create a ref that can be modified in tests
const mockInstalledPacksWithVersions = ref<{ id: string; version: string }[]>( const mockInstalledPacksWithVersions = ref<{ id: string; version: string }[]>(
@@ -143,35 +148,43 @@ describe('useConflictDetection', () => {
isReady: ref(false), isReady: ref(false),
isLoading: ref(false), isLoading: ref(false),
error: ref<unknown>(null) error: ref<unknown>(null)
} as unknown as ReturnType<typeof useInstalledPacks> } as Partial<ReturnType<typeof useInstalledPacks>> as ReturnType<
typeof useInstalledPacks
>
const mockManagerStore = { const mockManagerStore = {
isPackEnabled: vi.fn() isPackEnabled: vi.fn()
} as unknown as ReturnType<typeof useComfyManagerStore> } as Partial<ReturnType<typeof useComfyManagerStore>> as ReturnType<
typeof useComfyManagerStore
>
// Create refs that can be used to control computed properties // Create refs that can be used to control computed properties
const mockConflictedPackages = ref<ConflictDetectionResult[]>([]) let mockConflictedPackages: ConflictDetectionResult[] = []
const mockConflictStore = { const mockConflictStore = {
hasConflicts: computed(() => get hasConflicts() {
mockConflictedPackages.value.some((p) => p.has_conflict) return mockConflictedPackages.some((p) => p.has_conflict)
), },
conflictedPackages: mockConflictedPackages, get conflictedPackages() {
bannedPackages: computed(() => return mockConflictedPackages
mockConflictedPackages.value.filter((p) => },
get bannedPackages() {
return mockConflictedPackages.filter((p) =>
p.conflicts?.some((c) => c.type === 'banned') p.conflicts?.some((c) => c.type === 'banned')
) )
), },
securityPendingPackages: computed(() => get securityPendingPackages() {
mockConflictedPackages.value.filter((p) => return mockConflictedPackages.filter((p) =>
p.conflicts?.some((c) => c.type === 'pending') p.conflicts?.some((c) => c.type === 'pending')
) )
), },
setConflictedPackages: vi.fn(), setConflictedPackages: vi.fn(),
clearConflicts: vi.fn() clearConflicts: vi.fn()
} as unknown as ReturnType<typeof useConflictDetectionStore> } as Partial<ReturnType<typeof useConflictDetectionStore>> as ReturnType<
typeof useConflictDetectionStore
>
const mockIsInitialized = ref(true) const mockIsInitialized = true
const mockSystemStatsStore = { const mockSystemStatsStore = {
systemStats: { systemStats: {
system: { system: {
@@ -199,26 +212,26 @@ describe('useConflictDetection', () => {
] ]
}, },
isInitialized: mockIsInitialized, isInitialized: mockIsInitialized,
$state: {} as never,
$patch: vi.fn(),
$reset: vi.fn(),
$subscribe: vi.fn(),
$onAction: vi.fn(),
$dispose: vi.fn(),
$id: 'systemStats',
_customProperties: new Set<string>() _customProperties: new Set<string>()
} as unknown as ReturnType<typeof useSystemStatsStore> } as Partial<ReturnType<typeof useSystemStatsStore>> as ReturnType<
typeof useSystemStatsStore
>
const mockAcknowledgment = { const mockAcknowledgment = {
checkComfyUIVersionChange: vi.fn(), checkComfyUIVersionChange: vi.fn(),
acknowledgmentState: computed(() => ({})), acknowledgmentState: computed(
() => ({}) as Partial<ConflictAcknowledgmentState>
),
shouldShowConflictModal: computed(() => false), shouldShowConflictModal: computed(() => false),
shouldShowRedDot: computed(() => false), shouldShowRedDot: computed(() => false),
shouldShowManagerBanner: computed(() => false), shouldShowManagerBanner: computed(() => false),
dismissRedDotNotification: vi.fn(), dismissRedDotNotification: vi.fn(),
dismissWarningBanner: vi.fn(), dismissWarningBanner: vi.fn(),
markConflictsAsSeen: vi.fn() markConflictsAsSeen: vi.fn()
} as unknown as ReturnType<typeof useConflictAcknowledgment> } as Partial<ReturnType<typeof useConflictAcknowledgment>> as ReturnType<
typeof useConflictAcknowledgment
>
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks() vi.clearAllMocks()
@@ -249,7 +262,7 @@ describe('useConflictDetection', () => {
// Reset the installedPacksWithVersions data // Reset the installedPacksWithVersions data
mockInstalledPacksWithVersions.value = [] mockInstalledPacksWithVersions.value = []
// Reset conflicted packages // Reset conflicted packages
mockConflictedPackages.value = [] mockConflictedPackages = []
}) })
afterEach(() => { afterEach(() => {
@@ -414,7 +427,7 @@ describe('useConflictDetection', () => {
error: 'Import error', error: 'Import error',
name: 'fail-pack', name: 'fail-pack',
path: '/path/to/pack' path: '/path/to/pack'
} as any // The actual API returns different structure than types } as { error?: string; traceback?: string } | null // The actual API returns different structure than types
}) })
// Mock registry response for the package // Mock registry response for the package
@@ -437,7 +450,7 @@ describe('useConflictDetection', () => {
describe('computed properties', () => { describe('computed properties', () => {
it('should expose conflict status from store', () => { it('should expose conflict status from store', () => {
mockConflictedPackages.value = [ mockConflictedPackages = [
{ {
package_id: 'test', package_id: 'test',
package_name: 'Test', package_name: 'Test',
@@ -450,8 +463,8 @@ describe('useConflictDetection', () => {
useConflictDetection() useConflictDetection()
// The hasConflicts computed should be true since we have a conflict // The hasConflicts computed should be true since we have a conflict
expect(mockConflictedPackages.value).toHaveLength(1) expect(mockConflictedPackages).toHaveLength(1)
expect(mockConflictedPackages.value[0].has_conflict).toBe(true) expect(mockConflictedPackages[0].has_conflict).toBe(true)
}) })
}) })

View File

@@ -1,4 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { Ref } from 'vue'
import { ref } from 'vue' import { ref } from 'vue'
import { useManagerQueue } from '@/workbench/extensions/manager/composables/useManagerQueue' import { useManagerQueue } from '@/workbench/extensions/manager/composables/useManagerQueue'
@@ -22,9 +23,11 @@ type ManagerTaskHistory = Record<
type ManagerTaskQueue = components['schemas']['TaskStateMessage'] type ManagerTaskQueue = components['schemas']['TaskStateMessage']
describe('useManagerQueue', () => { describe('useManagerQueue', () => {
let taskHistory: any let taskHistory: Ref<ManagerTaskHistory>
let taskQueue: any let taskQueue: Ref<ManagerTaskQueue>
let installedPacks: any let installedPacks: Ref<
Record<string, components['schemas']['ManagerPackInstalled']>
>
const createManagerQueue = () => { const createManagerQueue = () => {
taskHistory = ref<ManagerTaskHistory>({}) taskHistory = ref<ManagerTaskHistory>({})
@@ -67,14 +70,28 @@ describe('useManagerQueue', () => {
{ {
ui_id: 'task1', ui_id: 'task1',
client_id: 'test-client-id', client_id: 'test-client-id',
task_name: 'Installing pack1' kind: 'install',
params: {
id: 'pack1',
version: '1.0.0',
selected_version: '1.0.0',
mode: 'remote' as const,
channel: 'default' as const
}
} }
] ]
taskQueue.value.pending_queue = [ taskQueue.value.pending_queue = [
{ {
ui_id: 'task2', ui_id: 'task2',
client_id: 'test-client-id', client_id: 'test-client-id',
task_name: 'Installing pack2' kind: 'install',
params: {
id: 'pack2',
version: '1.0.0',
selected_version: '1.0.0',
mode: 'remote' as const,
channel: 'default' as const
}
} }
] ]
@@ -101,12 +118,18 @@ describe('useManagerQueue', () => {
task1: { task1: {
ui_id: 'task1', ui_id: 'task1',
client_id: 'test-client-id', client_id: 'test-client-id',
status: { status_str: 'success', completed: true } kind: 'install',
timestamp: '2024-01-01T00:00:00Z',
result: 'success',
status: { status_str: 'success', completed: true, messages: [] }
}, },
task2: { task2: {
ui_id: 'task2', ui_id: 'task2',
client_id: 'test-client-id', client_id: 'test-client-id',
status: { status_str: 'success', completed: true } kind: 'install',
timestamp: '2024-01-01T00:00:00Z',
result: 'success',
status: { status_str: 'success', completed: true, messages: [] }
} }
} }
@@ -198,12 +221,12 @@ describe('useManagerQueue', () => {
it('handles empty installed_packs gracefully', () => { it('handles empty installed_packs gracefully', () => {
const queue = createManagerQueue() const queue = createManagerQueue()
const mockState: any = { const mockState = {
history: {}, history: {},
running_queue: [], running_queue: [],
pending_queue: [], pending_queue: [],
installed_packs: undefined installed_packs: undefined!
} } satisfies Partial<ManagerTaskQueue> as ManagerTaskQueue
// Just call the function - if it throws, the test will fail automatically // Just call the function - if it throws, the test will fail automatically
queue.updateTaskState(mockState) queue.updateTaskState(mockState)

View File

@@ -1,45 +1,45 @@
import { createTestingPinia } from '@pinia/testing'
import { setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest' import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ref } from 'vue'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { api } from '@/scripts/api' import { api } from '@/scripts/api'
import { useExtensionStore } from '@/stores/extensionStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore' import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { import {
ManagerUIState, ManagerUIState,
useManagerState useManagerState
} from '@/workbench/extensions/manager/composables/useManagerState' } from '@/workbench/extensions/manager/composables/useManagerState'
// Mock dependencies // Mock dependencies that are not stores
vi.mock('@/scripts/api', () => ({ vi.mock('@/scripts/api', () => ({
api: { api: {
getClientFeatureFlags: vi.fn(), getClientFeatureFlags: vi.fn(),
getServerFeature: vi.fn() getServerFeature: vi.fn(),
getSystemStats: vi.fn()
} }
})) }))
vi.mock('@/composables/useFeatureFlags', () => ({ vi.mock('@/composables/useFeatureFlags', () => {
useFeatureFlags: vi.fn(() => ({ const featureFlag = vi.fn()
flags: { supportsManagerV4: false }, return {
featureFlag: vi.fn() useFeatureFlags: vi.fn(() => ({
})) flags: { supportsManagerV4: false },
})) featureFlag
}))
}
})
vi.mock('@/stores/extensionStore', () => ({ vi.mock('@/services/dialogService', () => {
useExtensionStore: vi.fn() const showManagerPopup = vi.fn()
})) const showLegacyManagerPopup = vi.fn()
const showSettingsDialog = vi.fn()
vi.mock('@/stores/systemStatsStore', () => ({ return {
useSystemStatsStore: vi.fn() useDialogService: vi.fn(() => ({
})) showManagerPopup,
showLegacyManagerPopup,
vi.mock('@/services/dialogService', () => ({ showSettingsDialog
useDialogService: vi.fn(() => ({ }))
showManagerPopup: vi.fn(), }
showLegacyManagerPopup: vi.fn(), })
showSettingsDialog: vi.fn()
}))
}))
vi.mock('@/stores/commandStore', () => ({ vi.mock('@/stores/commandStore', () => ({
useCommandStore: vi.fn(() => ({ useCommandStore: vi.fn(() => ({
@@ -47,198 +47,223 @@ vi.mock('@/stores/commandStore', () => ({
})) }))
})) }))
vi.mock('@/stores/toastStore', () => ({ vi.mock('@/platform/updates/common/toastStore', () => {
useToastStore: vi.fn(() => ({ const add = vi.fn()
add: vi.fn() return {
})) useToastStore: vi.fn(() => ({
})) add
}))
}
})
vi.mock('@/workbench/extensions/manager/composables/useManagerDialog', () => ({ vi.mock('@/workbench/extensions/manager/composables/useManagerDialog', () => {
useManagerDialog: vi.fn(() => ({ const show = vi.fn()
show: vi.fn(), const hide = vi.fn()
hide: vi.fn() return {
})) useManagerDialog: vi.fn(() => ({
})) show,
hide
}))
}
})
describe('useManagerState', () => { describe('useManagerState', () => {
let systemStatsStore: ReturnType<typeof useSystemStatsStore>
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks() // Create a fresh testing pinia and activate it for each test
setActivePinia(
createTestingPinia({
stubActions: false,
createSpy: vi.fn
})
)
// Initialize stores
systemStatsStore = useSystemStatsStore()
// Reset all mocks
vi.resetAllMocks()
// Set default mock returns
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
vi.mocked(api.getServerFeature).mockReturnValue(undefined)
}) })
describe('managerUIState property', () => { describe('managerUIState property', () => {
it('should return DISABLED state when --enable-manager is NOT present', () => { it('should return DISABLED state when --enable-manager is NOT present', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref({ systemStatsStore.$patch({
system: { argv: ['python', 'main.py'] } // No --enable-manager flag systemStats: {
}), system: {
isInitialized: ref(true) os: 'Test OS',
} as any) python_version: '3.10',
vi.mocked(api.getClientFeatureFlags).mockReturnValue({}) embedded_python: false,
vi.mocked(useExtensionStore).mockReturnValue({ comfyui_version: '1.0.0',
extensions: [] pytorch_version: '2.0.0',
} as any) argv: ['python', 'main.py'], // No --enable-manager flag
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
const managerState = useManagerState() const managerState = useManagerState()
expect(managerState.managerUIState.value).toBe(ManagerUIState.DISABLED) expect(managerState.managerUIState.value).toBe(ManagerUIState.DISABLED)
}) })
it('should return LEGACY_UI state when --enable-manager-legacy-ui is present', () => { it('should return LEGACY_UI state when --enable-manager-legacy-ui is present', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref({ systemStatsStore.$patch({
systemStats: {
system: { system: {
os: 'Test OS',
python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: [ argv: [
'python', 'python',
'main.py', 'main.py',
'--enable-manager', '--enable-manager',
'--enable-manager-legacy-ui' '--enable-manager-legacy-ui'
] ],
} // Both flags needed ram_total: 16000000000,
}), ram_free: 8000000000
isInitialized: ref(true) },
} as any) devices: []
vi.mocked(api.getClientFeatureFlags).mockReturnValue({}) },
vi.mocked(useExtensionStore).mockReturnValue({ isInitialized: true
extensions: [] })
} as any)
const managerState = useManagerState() const managerState = useManagerState()
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI) expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
}) })
it('should return NEW_UI state when client and server both support v4', () => { it('should return NEW_UI state when client and server both support v4', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref({ systemStatsStore.$patch({
system: { argv: ['python', 'main.py', '--enable-manager'] } systemStats: {
}), // Need --enable-manager system: {
isInitialized: ref(true) os: 'Test OS',
} as any) python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: ['python', 'main.py', '--enable-manager'],
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
vi.mocked(api.getClientFeatureFlags).mockReturnValue({ vi.mocked(api.getClientFeatureFlags).mockReturnValue({
supports_manager_v4_ui: true supports_manager_v4_ui: true
}) })
vi.mocked(api.getServerFeature).mockReturnValue(true) vi.mocked(api.getServerFeature).mockReturnValue(true)
vi.mocked(useFeatureFlags).mockReturnValue({
flags: { supportsManagerV4: true },
featureFlag: vi.fn()
} as any)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState() const managerState = useManagerState()
expect(managerState.managerUIState.value).toBe(ManagerUIState.NEW_UI) expect(managerState.managerUIState.value).toBe(ManagerUIState.NEW_UI)
}) })
it('should return LEGACY_UI state when server supports v4 but client does not', () => { it('should return LEGACY_UI state when server supports v4 but client does not', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref({ systemStatsStore.$patch({
system: { argv: ['python', 'main.py', '--enable-manager'] } systemStats: {
}), // Need --enable-manager system: {
isInitialized: ref(true) os: 'Test OS',
} as any) python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: ['python', 'main.py', '--enable-manager'],
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
vi.mocked(api.getClientFeatureFlags).mockReturnValue({ vi.mocked(api.getClientFeatureFlags).mockReturnValue({
supports_manager_v4_ui: false supports_manager_v4_ui: false
}) })
vi.mocked(api.getServerFeature).mockReturnValue(true) vi.mocked(api.getServerFeature).mockReturnValue(true)
vi.mocked(useFeatureFlags).mockReturnValue({
flags: { supportsManagerV4: true },
featureFlag: vi.fn()
} as any)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState() const managerState = useManagerState()
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI) expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
}) })
it('should return LEGACY_UI state when legacy manager extension exists', () => { it('should return LEGACY_UI state when server does not support v4', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref({ systemStatsStore.$patch({
system: { argv: ['python', 'main.py', '--enable-manager'] } systemStats: {
}), // Need --enable-manager system: {
isInitialized: ref(true) os: 'Test OS',
} as any) python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: ['python', 'main.py', '--enable-manager'],
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
vi.mocked(api.getClientFeatureFlags).mockReturnValue({}) vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
vi.mocked(useFeatureFlags).mockReturnValue({ vi.mocked(api.getServerFeature).mockReturnValue(false)
flags: { supportsManagerV4: false },
featureFlag: vi.fn()
} as any)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: [{ name: 'Comfy.CustomNodesManager' }]
} as any)
const managerState = useManagerState() const managerState = useManagerState()
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI) expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
}) })
it('should return NEW_UI state when server feature flags are undefined', () => { it('should return NEW_UI state when server feature flags are undefined', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref({ systemStatsStore.$patch({
system: { argv: ['python', 'main.py', '--enable-manager'] } systemStats: {
}), // Need --enable-manager system: {
isInitialized: ref(true) os: 'Test OS',
} as any) python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: ['python', 'main.py', '--enable-manager'],
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
vi.mocked(api.getClientFeatureFlags).mockReturnValue({}) vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
vi.mocked(api.getServerFeature).mockReturnValue(undefined) vi.mocked(api.getServerFeature).mockReturnValue(undefined)
vi.mocked(useFeatureFlags).mockReturnValue({
flags: { supportsManagerV4: undefined },
featureFlag: vi.fn()
} as any)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState() const managerState = useManagerState()
// When server feature flags haven't loaded yet, default to NEW_UI
expect(managerState.managerUIState.value).toBe(ManagerUIState.NEW_UI) expect(managerState.managerUIState.value).toBe(ManagerUIState.NEW_UI)
}) })
it('should return LEGACY_UI state when server does not support v4', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
system: { argv: ['python', 'main.py', '--enable-manager'] }
}), // Need --enable-manager
isInitialized: ref(true)
} as any)
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
vi.mocked(api.getServerFeature).mockReturnValue(false)
vi.mocked(useFeatureFlags).mockReturnValue({
flags: { supportsManagerV4: false },
featureFlag: vi.fn()
} as any)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState()
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
})
it('should handle null systemStats gracefully', () => { it('should handle null systemStats gracefully', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref(null), systemStatsStore.$patch({
isInitialized: ref(true) systemStats: null,
} as any) isInitialized: true
})
vi.mocked(api.getClientFeatureFlags).mockReturnValue({ vi.mocked(api.getClientFeatureFlags).mockReturnValue({
supports_manager_v4_ui: true supports_manager_v4_ui: true
}) })
vi.mocked(api.getServerFeature).mockReturnValue(true) vi.mocked(api.getServerFeature).mockReturnValue(true)
vi.mocked(useFeatureFlags).mockReturnValue({
flags: { supportsManagerV4: true },
featureFlag: vi.fn()
} as any)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState() const managerState = useManagerState()
// When systemStats is null, we can't check for --enable-manager flag, so manager is disabled // When systemStats is null, we can't check for --enable-manager flag, so manager is disabled
expect(managerState.managerUIState.value).toBe(ManagerUIState.DISABLED) expect(managerState.managerUIState.value).toBe(ManagerUIState.DISABLED)
}) })
@@ -246,115 +271,163 @@ describe('useManagerState', () => {
describe('helper properties', () => { describe('helper properties', () => {
it('isManagerEnabled should return true when state is not DISABLED', () => { it('isManagerEnabled should return true when state is not DISABLED', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref({ systemStatsStore.$patch({
system: { argv: ['python', 'main.py', '--enable-manager'] } systemStats: {
}), // Need --enable-manager system: {
isInitialized: ref(true) os: 'Test OS',
} as any) python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: ['python', 'main.py', '--enable-manager'],
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
vi.mocked(api.getClientFeatureFlags).mockReturnValue({ vi.mocked(api.getClientFeatureFlags).mockReturnValue({
supports_manager_v4_ui: true supports_manager_v4_ui: true
}) })
vi.mocked(api.getServerFeature).mockReturnValue(true) vi.mocked(api.getServerFeature).mockReturnValue(true)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState() const managerState = useManagerState()
expect(managerState.isManagerEnabled.value).toBe(true) expect(managerState.isManagerEnabled.value).toBe(true)
}) })
it('isManagerEnabled should return false when state is DISABLED', () => { it('isManagerEnabled should return false when state is DISABLED', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref({ systemStatsStore.$patch({
system: { argv: ['python', 'main.py'] } // No --enable-manager flag means disabled systemStats: {
}), system: {
isInitialized: ref(true) os: 'Test OS',
} as any) python_version: '3.10',
vi.mocked(api.getClientFeatureFlags).mockReturnValue({}) embedded_python: false,
vi.mocked(useExtensionStore).mockReturnValue({ comfyui_version: '1.0.0',
extensions: [] pytorch_version: '2.0.0',
} as any) argv: ['python', 'main.py'], // No --enable-manager flag
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
const managerState = useManagerState() const managerState = useManagerState()
expect(managerState.isManagerEnabled.value).toBe(false) expect(managerState.isManagerEnabled.value).toBe(false)
}) })
it('isNewManagerUI should return true when state is NEW_UI', () => { it('isNewManagerUI should return true when state is NEW_UI', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref({ systemStatsStore.$patch({
system: { argv: ['python', 'main.py', '--enable-manager'] } systemStats: {
}), // Need --enable-manager system: {
isInitialized: ref(true) os: 'Test OS',
} as any) python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: ['python', 'main.py', '--enable-manager'],
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
vi.mocked(api.getClientFeatureFlags).mockReturnValue({ vi.mocked(api.getClientFeatureFlags).mockReturnValue({
supports_manager_v4_ui: true supports_manager_v4_ui: true
}) })
vi.mocked(api.getServerFeature).mockReturnValue(true) vi.mocked(api.getServerFeature).mockReturnValue(true)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState() const managerState = useManagerState()
expect(managerState.isNewManagerUI.value).toBe(true) expect(managerState.isNewManagerUI.value).toBe(true)
}) })
it('isLegacyManagerUI should return true when state is LEGACY_UI', () => { it('isLegacyManagerUI should return true when state is LEGACY_UI', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref({ systemStatsStore.$patch({
systemStats: {
system: { system: {
os: 'Test OS',
python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: [ argv: [
'python', 'python',
'main.py', 'main.py',
'--enable-manager', '--enable-manager',
'--enable-manager-legacy-ui' '--enable-manager-legacy-ui'
] ],
} // Both flags needed ram_total: 16000000000,
}), ram_free: 8000000000
isInitialized: ref(true) },
} as any) devices: []
vi.mocked(api.getClientFeatureFlags).mockReturnValue({}) },
vi.mocked(useExtensionStore).mockReturnValue({ isInitialized: true
extensions: [] })
} as any)
const managerState = useManagerState() const managerState = useManagerState()
expect(managerState.isLegacyManagerUI.value).toBe(true) expect(managerState.isLegacyManagerUI.value).toBe(true)
}) })
it('shouldShowInstallButton should return true only for NEW_UI', () => { it('shouldShowInstallButton should return true only for NEW_UI', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref({ systemStatsStore.$patch({
system: { argv: ['python', 'main.py', '--enable-manager'] } systemStats: {
}), // Need --enable-manager system: {
isInitialized: ref(true) os: 'Test OS',
} as any) python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: ['python', 'main.py', '--enable-manager'],
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
vi.mocked(api.getClientFeatureFlags).mockReturnValue({ vi.mocked(api.getClientFeatureFlags).mockReturnValue({
supports_manager_v4_ui: true supports_manager_v4_ui: true
}) })
vi.mocked(api.getServerFeature).mockReturnValue(true) vi.mocked(api.getServerFeature).mockReturnValue(true)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState() const managerState = useManagerState()
expect(managerState.shouldShowInstallButton.value).toBe(true) expect(managerState.shouldShowInstallButton.value).toBe(true)
}) })
it('shouldShowManagerButtons should return true when not DISABLED', () => { it('shouldShowManagerButtons should return true when not DISABLED', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({ // Set up store state
systemStats: ref({ systemStatsStore.$patch({
system: { argv: ['python', 'main.py', '--enable-manager'] } systemStats: {
}), // Need --enable-manager system: {
isInitialized: ref(true) os: 'Test OS',
} as any) python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: ['python', 'main.py', '--enable-manager'],
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
vi.mocked(api.getClientFeatureFlags).mockReturnValue({ vi.mocked(api.getClientFeatureFlags).mockReturnValue({
supports_manager_v4_ui: true supports_manager_v4_ui: true
}) })
vi.mocked(api.getServerFeature).mockReturnValue(true) vi.mocked(api.getServerFeature).mockReturnValue(true)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState() const managerState = useManagerState()
expect(managerState.shouldShowManagerButtons.value).toBe(true) expect(managerState.shouldShowManagerButtons.value).toBe(true)

View File

@@ -27,7 +27,7 @@ describe('versionUtil', () => {
it('should return null when current version is null', () => { it('should return null when current version is null', () => {
const result = checkVersionCompatibility( const result = checkVersionCompatibility(
'comfyui_version', 'comfyui_version',
null as any, null!,
'>=1.0.0' '>=1.0.0'
) )
expect(result).toBeNull() expect(result).toBeNull()
@@ -51,7 +51,7 @@ describe('versionUtil', () => {
const result = checkVersionCompatibility( const result = checkVersionCompatibility(
'comfyui_version', 'comfyui_version',
'1.0.0', '1.0.0',
null as any null!
) )
expect(result).toBeNull() expect(result).toBeNull()
}) })