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
}
function isFuseSearchable(item: any): item is FuseSearchable {
return 'postProcessSearchScores' in item
function isFuseSearchable(item: unknown): item is FuseSearchable {
return (
typeof item === 'object' &&
item !== null &&
'postProcessSearchScores' in item
)
}
/**

View File

@@ -28,6 +28,7 @@ import {
triggerCallbackOnAllNodes,
visitGraphNodes
} from '@/utils/graphTraversalUtil'
import { createMockLGraphNode } from './__tests__/litegraphTestUtils'
// Mock node factory
function createMockNode(
@@ -39,13 +40,13 @@ function createMockNode(
graph?: LGraph
} = {}
): LGraphNode {
const node = {
const node = createMockLGraphNode({
id,
isSubgraphNode: options.isSubgraph ? () => true : undefined,
subgraph: options.subgraph,
onExecutionStart: options.callback,
graph: options.graph
} as unknown as LGraphNode
}) satisfies Partial<LGraphNode> as LGraphNode
options.graph?.nodes?.push(node)
return node
}
@@ -58,7 +59,7 @@ function createMockGraph(nodes: LGraphNode[]): LGraph {
isRootGraph: true,
getNodeById: (id: string | number) =>
nodes.find((n) => String(n.id) === String(id)) || null
} as unknown as LGraph
} satisfies Partial<LGraph> as LGraph
}
// Mock subgraph factory
@@ -75,7 +76,7 @@ function createMockSubgraph(
rootGraph,
getNodeById: (nodeId: string | number) =>
nodes.find((n) => String(n.id) === String(nodeId)) || null
} as unknown as Subgraph
} satisfies Partial<Subgraph> as Subgraph
return graph
}
@@ -96,8 +97,8 @@ describe('graphTraversalUtil', () => {
it('should return null for invalid input', () => {
expect(parseExecutionId('')).toBeNull()
expect(parseExecutionId(null as any)).toBeNull()
expect(parseExecutionId(undefined as any)).toBeNull()
expect(parseExecutionId(null!)).toBeNull()
expect(parseExecutionId(undefined!)).toBeNull()
})
})
@@ -415,7 +416,7 @@ describe('graphTraversalUtil', () => {
// Add a title property to each node
forEachNode(graph, (node) => {
;(node as any).title = `Node ${node.id}`
node.title = `Node ${node.id}`
})
expect(nodes[0]).toHaveProperty('title', 'Node 1')
@@ -653,7 +654,7 @@ describe('graphTraversalUtil', () => {
it('should return root graph from subgraph', () => {
const rootGraph = createMockGraph([])
const subgraph = createMockSubgraph('sub-uuid', [])
;(subgraph as any).rootGraph = rootGraph
;(subgraph as Subgraph & { rootGraph: LGraph }).rootGraph = rootGraph
expect(getRootGraph(subgraph)).toBe(rootGraph)
})
@@ -662,9 +663,10 @@ describe('graphTraversalUtil', () => {
const rootGraph = createMockGraph([])
const midSubgraph = createMockSubgraph('mid-uuid', [])
const deepSubgraph = createMockSubgraph('deep-uuid', [])
;(midSubgraph as any).rootGraph = rootGraph
;(deepSubgraph as any).rootGraph = midSubgraph
;(midSubgraph as Subgraph & { rootGraph: LGraph }).rootGraph = rootGraph
;(
deepSubgraph as Subgraph & { rootGraph: LGraph | Subgraph }
).rootGraph = midSubgraph
expect(getRootGraph(deepSubgraph)).toBe(rootGraph)
})
@@ -726,7 +728,7 @@ describe('graphTraversalUtil', () => {
const graph = createMockGraph(nodes)
forEachSubgraphNode(graph, subgraphId, (node) => {
;(node as any).title = 'Updated Title'
node.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
* SOFTWARE.
*/
import type { INodeOutputSlot } from '@/lib/litegraph/src/interfaces'
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
import type { SerialisedLLinkArray } from '@/lib/litegraph/src/LLink'
import type { LGraph, LGraphNode, LLink } from '@/lib/litegraph/src/litegraph'
@@ -79,12 +80,12 @@ export function fixBadLinks(
options: {
fix?: boolean
silent?: boolean
logger?: { log: (...args: any[]) => void }
logger?: { log: (...args: unknown[]) => void }
} = {}
): BadLinksData {
const { fix = false, silent = false, logger: _logger = console } = options
const logger = {
log: (...args: any[]) => {
log: (...args: unknown[]) => {
if (!silent) {
_logger.log(...args)
}
@@ -166,7 +167,9 @@ export function fixBadLinks(
patchedNode['outputs']![slot]!['links'].push(linkId)
if (fix) {
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!.push(linkId)
}
@@ -428,7 +431,7 @@ export function fixBadLinks(
(l) =>
l &&
(l[0] === data.deletedLinks[i] ||
(l as any).id === data.deletedLinks[i])
('id' in l && l.id === data.deletedLinks[i]))
)
if (idx === -1) {
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: 'anotherNormal', type: 'number' }
] as unknown as IWidget[]
] as Partial<IWidget>[] as IWidget[]
const widgetValues = [42, 'dummy value', 3.14]
@@ -56,7 +56,7 @@ describe('migrateWidgetsValues', () => {
it('should handle empty widgets and values', () => {
const inputDefs: Record<string, InputSpec> = {}
const widgets: IWidget[] = []
const widgetValues: any[] = []
const widgetValues: unknown[] = []
const result = migrateWidgetsValues(inputDefs, widgets, widgetValues)
expect(result).toEqual([])
@@ -79,10 +79,10 @@ describe('migrateWidgetsValues', () => {
}
}
const widgets: IWidget[] = [
const widgets = [
{ name: 'first', type: 'number' },
{ name: 'last', type: 'number' }
] as unknown as IWidget[]
] as Partial<IWidget>[] as IWidget[]
const widgetValues = ['first value', 'dummy', 'last value']
@@ -93,7 +93,8 @@ describe('migrateWidgetsValues', () => {
describe('compressWidgetInputSlots', () => {
it('should remove unconnected widget input slots', () => {
const graph: ISerialisedGraph = {
// Using partial mock - only including properties needed for test
const graph = {
nodes: [
{
id: 1,
@@ -112,7 +113,7 @@ describe('compressWidgetInputSlots', () => {
}
],
links: [[2, 1, 0, 1, 0, 'INT']]
} as unknown as ISerialisedGraph
} as Partial<ISerialisedGraph> as ISerialisedGraph
compressWidgetInputSlots(graph)
@@ -122,7 +123,7 @@ describe('compressWidgetInputSlots', () => {
})
it('should update link target slots correctly', () => {
const graph: ISerialisedGraph = {
const graph = {
nodes: [
{
id: 1,
@@ -144,7 +145,7 @@ describe('compressWidgetInputSlots', () => {
[2, 1, 0, 1, 1, 'INT'],
[3, 1, 0, 1, 2, 'INT']
]
} as unknown as ISerialisedGraph
} as Partial<ISerialisedGraph> as ISerialisedGraph
compressWidgetInputSlots(graph)
@@ -160,10 +161,11 @@ describe('compressWidgetInputSlots', () => {
})
it('should handle graphs with no nodes gracefully', () => {
const graph: ISerialisedGraph = {
// Using partial mock - only including properties needed for test
const graph = {
nodes: [],
links: []
} as unknown as ISerialisedGraph
} as Partial<ISerialisedGraph> as ISerialisedGraph
compressWidgetInputSlots(graph)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ import { useSystemStatsStore } from '@/stores/systemStatsStore'
import type { components } from '@/types/comfyRegistryTypes'
import { useInstalledPacks } from '@/workbench/extensions/manager/composables/nodePack/useInstalledPacks'
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 { useComfyManagerService } from '@/workbench/extensions/manager/services/comfyManagerService'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
@@ -121,13 +122,17 @@ describe('useConflictDetection', () => {
getImportFailInfoBulk: vi.fn(),
isLoading: ref(false),
error: ref<string | null>(null)
} as unknown as ReturnType<typeof useComfyManagerService>
} as Partial<ReturnType<typeof useComfyManagerService>> as ReturnType<
typeof useComfyManagerService
>
const mockRegistryService = {
getBulkNodeVersions: vi.fn(),
isLoading: ref(false),
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
const mockInstalledPacksWithVersions = ref<{ id: string; version: string }[]>(
@@ -143,35 +148,43 @@ describe('useConflictDetection', () => {
isReady: ref(false),
isLoading: ref(false),
error: ref<unknown>(null)
} as unknown as ReturnType<typeof useInstalledPacks>
} as Partial<ReturnType<typeof useInstalledPacks>> as ReturnType<
typeof useInstalledPacks
>
const mockManagerStore = {
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
const mockConflictedPackages = ref<ConflictDetectionResult[]>([])
let mockConflictedPackages: ConflictDetectionResult[] = []
const mockConflictStore = {
hasConflicts: computed(() =>
mockConflictedPackages.value.some((p) => p.has_conflict)
),
conflictedPackages: mockConflictedPackages,
bannedPackages: computed(() =>
mockConflictedPackages.value.filter((p) =>
get hasConflicts() {
return mockConflictedPackages.some((p) => p.has_conflict)
},
get conflictedPackages() {
return mockConflictedPackages
},
get bannedPackages() {
return mockConflictedPackages.filter((p) =>
p.conflicts?.some((c) => c.type === 'banned')
)
),
securityPendingPackages: computed(() =>
mockConflictedPackages.value.filter((p) =>
},
get securityPendingPackages() {
return mockConflictedPackages.filter((p) =>
p.conflicts?.some((c) => c.type === 'pending')
)
),
},
setConflictedPackages: 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 = {
systemStats: {
system: {
@@ -199,26 +212,26 @@ describe('useConflictDetection', () => {
]
},
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>()
} as unknown as ReturnType<typeof useSystemStatsStore>
} as Partial<ReturnType<typeof useSystemStatsStore>> as ReturnType<
typeof useSystemStatsStore
>
const mockAcknowledgment = {
checkComfyUIVersionChange: vi.fn(),
acknowledgmentState: computed(() => ({})),
acknowledgmentState: computed(
() => ({}) as Partial<ConflictAcknowledgmentState>
),
shouldShowConflictModal: computed(() => false),
shouldShowRedDot: computed(() => false),
shouldShowManagerBanner: computed(() => false),
dismissRedDotNotification: vi.fn(),
dismissWarningBanner: vi.fn(),
markConflictsAsSeen: vi.fn()
} as unknown as ReturnType<typeof useConflictAcknowledgment>
} as Partial<ReturnType<typeof useConflictAcknowledgment>> as ReturnType<
typeof useConflictAcknowledgment
>
beforeEach(() => {
vi.clearAllMocks()
@@ -249,7 +262,7 @@ describe('useConflictDetection', () => {
// Reset the installedPacksWithVersions data
mockInstalledPacksWithVersions.value = []
// Reset conflicted packages
mockConflictedPackages.value = []
mockConflictedPackages = []
})
afterEach(() => {
@@ -414,7 +427,7 @@ describe('useConflictDetection', () => {
error: 'Import error',
name: 'fail-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
@@ -437,7 +450,7 @@ describe('useConflictDetection', () => {
describe('computed properties', () => {
it('should expose conflict status from store', () => {
mockConflictedPackages.value = [
mockConflictedPackages = [
{
package_id: 'test',
package_name: 'Test',
@@ -450,8 +463,8 @@ describe('useConflictDetection', () => {
useConflictDetection()
// The hasConflicts computed should be true since we have a conflict
expect(mockConflictedPackages.value).toHaveLength(1)
expect(mockConflictedPackages.value[0].has_conflict).toBe(true)
expect(mockConflictedPackages).toHaveLength(1)
expect(mockConflictedPackages[0].has_conflict).toBe(true)
})
})

View File

@@ -1,4 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { Ref } from 'vue'
import { ref } from 'vue'
import { useManagerQueue } from '@/workbench/extensions/manager/composables/useManagerQueue'
@@ -22,9 +23,11 @@ type ManagerTaskHistory = Record<
type ManagerTaskQueue = components['schemas']['TaskStateMessage']
describe('useManagerQueue', () => {
let taskHistory: any
let taskQueue: any
let installedPacks: any
let taskHistory: Ref<ManagerTaskHistory>
let taskQueue: Ref<ManagerTaskQueue>
let installedPacks: Ref<
Record<string, components['schemas']['ManagerPackInstalled']>
>
const createManagerQueue = () => {
taskHistory = ref<ManagerTaskHistory>({})
@@ -67,14 +70,28 @@ describe('useManagerQueue', () => {
{
ui_id: 'task1',
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 = [
{
ui_id: 'task2',
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: {
ui_id: 'task1',
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: {
ui_id: 'task2',
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', () => {
const queue = createManagerQueue()
const mockState: any = {
const mockState = {
history: {},
running_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
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 { ref } from 'vue'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { api } from '@/scripts/api'
import { useExtensionStore } from '@/stores/extensionStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import {
ManagerUIState,
useManagerState
} from '@/workbench/extensions/manager/composables/useManagerState'
// Mock dependencies
// Mock dependencies that are not stores
vi.mock('@/scripts/api', () => ({
api: {
getClientFeatureFlags: vi.fn(),
getServerFeature: vi.fn()
getServerFeature: vi.fn(),
getSystemStats: vi.fn()
}
}))
vi.mock('@/composables/useFeatureFlags', () => ({
useFeatureFlags: vi.fn(() => ({
flags: { supportsManagerV4: false },
featureFlag: vi.fn()
}))
}))
vi.mock('@/composables/useFeatureFlags', () => {
const featureFlag = vi.fn()
return {
useFeatureFlags: vi.fn(() => ({
flags: { supportsManagerV4: false },
featureFlag
}))
}
})
vi.mock('@/stores/extensionStore', () => ({
useExtensionStore: vi.fn()
}))
vi.mock('@/stores/systemStatsStore', () => ({
useSystemStatsStore: vi.fn()
}))
vi.mock('@/services/dialogService', () => ({
useDialogService: vi.fn(() => ({
showManagerPopup: vi.fn(),
showLegacyManagerPopup: vi.fn(),
showSettingsDialog: vi.fn()
}))
}))
vi.mock('@/services/dialogService', () => {
const showManagerPopup = vi.fn()
const showLegacyManagerPopup = vi.fn()
const showSettingsDialog = vi.fn()
return {
useDialogService: vi.fn(() => ({
showManagerPopup,
showLegacyManagerPopup,
showSettingsDialog
}))
}
})
vi.mock('@/stores/commandStore', () => ({
useCommandStore: vi.fn(() => ({
@@ -47,198 +47,223 @@ vi.mock('@/stores/commandStore', () => ({
}))
}))
vi.mock('@/stores/toastStore', () => ({
useToastStore: vi.fn(() => ({
add: vi.fn()
}))
}))
vi.mock('@/platform/updates/common/toastStore', () => {
const add = vi.fn()
return {
useToastStore: vi.fn(() => ({
add
}))
}
})
vi.mock('@/workbench/extensions/manager/composables/useManagerDialog', () => ({
useManagerDialog: vi.fn(() => ({
show: vi.fn(),
hide: vi.fn()
}))
}))
vi.mock('@/workbench/extensions/manager/composables/useManagerDialog', () => {
const show = vi.fn()
const hide = vi.fn()
return {
useManagerDialog: vi.fn(() => ({
show,
hide
}))
}
})
describe('useManagerState', () => {
let systemStatsStore: ReturnType<typeof useSystemStatsStore>
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', () => {
it('should return DISABLED state when --enable-manager is NOT present', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
system: { argv: ['python', 'main.py'] } // No --enable-manager flag
}),
isInitialized: ref(true)
} as any)
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
// Set up store state
systemStatsStore.$patch({
systemStats: {
system: {
os: 'Test OS',
python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: ['python', 'main.py'], // No --enable-manager flag
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
const managerState = useManagerState()
expect(managerState.managerUIState.value).toBe(ManagerUIState.DISABLED)
})
it('should return LEGACY_UI state when --enable-manager-legacy-ui is present', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
// Set up store state
systemStatsStore.$patch({
systemStats: {
system: {
os: 'Test OS',
python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: [
'python',
'main.py',
'--enable-manager',
'--enable-manager-legacy-ui'
]
} // Both flags needed
}),
isInitialized: ref(true)
} as any)
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
],
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
const managerState = useManagerState()
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
})
it('should return NEW_UI state when client and server both support v4', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
system: { argv: ['python', 'main.py', '--enable-manager'] }
}), // Need --enable-manager
isInitialized: ref(true)
} as any)
// Set up store state
systemStatsStore.$patch({
systemStats: {
system: {
os: 'Test OS',
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({
supports_manager_v4_ui: 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()
expect(managerState.managerUIState.value).toBe(ManagerUIState.NEW_UI)
})
it('should return LEGACY_UI state when server supports v4 but client does not', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
system: { argv: ['python', 'main.py', '--enable-manager'] }
}), // Need --enable-manager
isInitialized: ref(true)
} as any)
// Set up store state
systemStatsStore.$patch({
systemStats: {
system: {
os: 'Test OS',
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({
supports_manager_v4_ui: false
})
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()
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
})
it('should return LEGACY_UI state when legacy manager extension exists', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
system: { argv: ['python', 'main.py', '--enable-manager'] }
}), // Need --enable-manager
isInitialized: ref(true)
} as any)
it('should return LEGACY_UI state when server does not support v4', () => {
// Set up store state
systemStatsStore.$patch({
systemStats: {
system: {
os: 'Test OS',
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(useFeatureFlags).mockReturnValue({
flags: { supportsManagerV4: false },
featureFlag: vi.fn()
} as any)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: [{ name: 'Comfy.CustomNodesManager' }]
} as any)
vi.mocked(api.getServerFeature).mockReturnValue(false)
const managerState = useManagerState()
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
})
it('should return NEW_UI state when server feature flags are undefined', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
system: { argv: ['python', 'main.py', '--enable-manager'] }
}), // Need --enable-manager
isInitialized: ref(true)
} as any)
// Set up store state
systemStatsStore.$patch({
systemStats: {
system: {
os: 'Test OS',
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.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()
// When server feature flags haven't loaded yet, default to 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', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref(null),
isInitialized: ref(true)
} as any)
// Set up store state
systemStatsStore.$patch({
systemStats: null,
isInitialized: true
})
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
supports_manager_v4_ui: 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()
// When systemStats is null, we can't check for --enable-manager flag, so manager is disabled
expect(managerState.managerUIState.value).toBe(ManagerUIState.DISABLED)
})
@@ -246,115 +271,163 @@ describe('useManagerState', () => {
describe('helper properties', () => {
it('isManagerEnabled should return true when state is not DISABLED', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
system: { argv: ['python', 'main.py', '--enable-manager'] }
}), // Need --enable-manager
isInitialized: ref(true)
} as any)
// Set up store state
systemStatsStore.$patch({
systemStats: {
system: {
os: 'Test OS',
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({
supports_manager_v4_ui: true
})
vi.mocked(api.getServerFeature).mockReturnValue(true)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState()
expect(managerState.isManagerEnabled.value).toBe(true)
})
it('isManagerEnabled should return false when state is DISABLED', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
system: { argv: ['python', 'main.py'] } // No --enable-manager flag means disabled
}),
isInitialized: ref(true)
} as any)
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
// Set up store state
systemStatsStore.$patch({
systemStats: {
system: {
os: 'Test OS',
python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: ['python', 'main.py'], // No --enable-manager flag
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
const managerState = useManagerState()
expect(managerState.isManagerEnabled.value).toBe(false)
})
it('isNewManagerUI should return true when state is NEW_UI', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
system: { argv: ['python', 'main.py', '--enable-manager'] }
}), // Need --enable-manager
isInitialized: ref(true)
} as any)
// Set up store state
systemStatsStore.$patch({
systemStats: {
system: {
os: 'Test OS',
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({
supports_manager_v4_ui: true
})
vi.mocked(api.getServerFeature).mockReturnValue(true)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState()
expect(managerState.isNewManagerUI.value).toBe(true)
})
it('isLegacyManagerUI should return true when state is LEGACY_UI', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
// Set up store state
systemStatsStore.$patch({
systemStats: {
system: {
os: 'Test OS',
python_version: '3.10',
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
argv: [
'python',
'main.py',
'--enable-manager',
'--enable-manager-legacy-ui'
]
} // Both flags needed
}),
isInitialized: ref(true)
} as any)
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
],
ram_total: 16000000000,
ram_free: 8000000000
},
devices: []
},
isInitialized: true
})
const managerState = useManagerState()
expect(managerState.isLegacyManagerUI.value).toBe(true)
})
it('shouldShowInstallButton should return true only for NEW_UI', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
system: { argv: ['python', 'main.py', '--enable-manager'] }
}), // Need --enable-manager
isInitialized: ref(true)
} as any)
// Set up store state
systemStatsStore.$patch({
systemStats: {
system: {
os: 'Test OS',
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({
supports_manager_v4_ui: true
})
vi.mocked(api.getServerFeature).mockReturnValue(true)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState()
expect(managerState.shouldShowInstallButton.value).toBe(true)
})
it('shouldShowManagerButtons should return true when not DISABLED', () => {
vi.mocked(useSystemStatsStore).mockReturnValue({
systemStats: ref({
system: { argv: ['python', 'main.py', '--enable-manager'] }
}), // Need --enable-manager
isInitialized: ref(true)
} as any)
// Set up store state
systemStatsStore.$patch({
systemStats: {
system: {
os: 'Test OS',
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({
supports_manager_v4_ui: true
})
vi.mocked(api.getServerFeature).mockReturnValue(true)
vi.mocked(useExtensionStore).mockReturnValue({
extensions: []
} as any)
const managerState = useManagerState()
expect(managerState.shouldShowManagerButtons.value).toBe(true)

View File

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