mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 07:30:11 +00:00
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:
committed by
GitHub
parent
59c58379fe
commit
a64c561a5f
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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]}`)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) => ({
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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')
|
||||
])
|
||||
|
||||
|
||||
@@ -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')
|
||||
])
|
||||
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user