mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 18:52:19 +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
|
postProcessSearchScores: (scores: SearchAuxScore) => SearchAuxScore
|
||||||
}
|
}
|
||||||
|
|
||||||
function isFuseSearchable(item: any): item is FuseSearchable {
|
function isFuseSearchable(item: unknown): item is FuseSearchable {
|
||||||
return 'postProcessSearchScores' in item
|
return (
|
||||||
|
typeof item === 'object' &&
|
||||||
|
item !== null &&
|
||||||
|
'postProcessSearchScores' in item
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
triggerCallbackOnAllNodes,
|
triggerCallbackOnAllNodes,
|
||||||
visitGraphNodes
|
visitGraphNodes
|
||||||
} from '@/utils/graphTraversalUtil'
|
} from '@/utils/graphTraversalUtil'
|
||||||
|
import { createMockLGraphNode } from './__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
// Mock node factory
|
// Mock node factory
|
||||||
function createMockNode(
|
function createMockNode(
|
||||||
@@ -39,13 +40,13 @@ function createMockNode(
|
|||||||
graph?: LGraph
|
graph?: LGraph
|
||||||
} = {}
|
} = {}
|
||||||
): LGraphNode {
|
): LGraphNode {
|
||||||
const node = {
|
const node = createMockLGraphNode({
|
||||||
id,
|
id,
|
||||||
isSubgraphNode: options.isSubgraph ? () => true : undefined,
|
isSubgraphNode: options.isSubgraph ? () => true : undefined,
|
||||||
subgraph: options.subgraph,
|
subgraph: options.subgraph,
|
||||||
onExecutionStart: options.callback,
|
onExecutionStart: options.callback,
|
||||||
graph: options.graph
|
graph: options.graph
|
||||||
} as unknown as LGraphNode
|
}) satisfies Partial<LGraphNode> as LGraphNode
|
||||||
options.graph?.nodes?.push(node)
|
options.graph?.nodes?.push(node)
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
@@ -58,7 +59,7 @@ function createMockGraph(nodes: LGraphNode[]): LGraph {
|
|||||||
isRootGraph: true,
|
isRootGraph: true,
|
||||||
getNodeById: (id: string | number) =>
|
getNodeById: (id: string | number) =>
|
||||||
nodes.find((n) => String(n.id) === String(id)) || null
|
nodes.find((n) => String(n.id) === String(id)) || null
|
||||||
} as unknown as LGraph
|
} satisfies Partial<LGraph> as LGraph
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock subgraph factory
|
// Mock subgraph factory
|
||||||
@@ -75,7 +76,7 @@ function createMockSubgraph(
|
|||||||
rootGraph,
|
rootGraph,
|
||||||
getNodeById: (nodeId: string | number) =>
|
getNodeById: (nodeId: string | number) =>
|
||||||
nodes.find((n) => String(n.id) === String(nodeId)) || null
|
nodes.find((n) => String(n.id) === String(nodeId)) || null
|
||||||
} as unknown as Subgraph
|
} satisfies Partial<Subgraph> as Subgraph
|
||||||
return graph
|
return graph
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +97,8 @@ describe('graphTraversalUtil', () => {
|
|||||||
|
|
||||||
it('should return null for invalid input', () => {
|
it('should return null for invalid input', () => {
|
||||||
expect(parseExecutionId('')).toBeNull()
|
expect(parseExecutionId('')).toBeNull()
|
||||||
expect(parseExecutionId(null as any)).toBeNull()
|
expect(parseExecutionId(null!)).toBeNull()
|
||||||
expect(parseExecutionId(undefined as any)).toBeNull()
|
expect(parseExecutionId(undefined!)).toBeNull()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -415,7 +416,7 @@ describe('graphTraversalUtil', () => {
|
|||||||
|
|
||||||
// Add a title property to each node
|
// Add a title property to each node
|
||||||
forEachNode(graph, (node) => {
|
forEachNode(graph, (node) => {
|
||||||
;(node as any).title = `Node ${node.id}`
|
node.title = `Node ${node.id}`
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(nodes[0]).toHaveProperty('title', 'Node 1')
|
expect(nodes[0]).toHaveProperty('title', 'Node 1')
|
||||||
@@ -653,7 +654,7 @@ describe('graphTraversalUtil', () => {
|
|||||||
it('should return root graph from subgraph', () => {
|
it('should return root graph from subgraph', () => {
|
||||||
const rootGraph = createMockGraph([])
|
const rootGraph = createMockGraph([])
|
||||||
const subgraph = createMockSubgraph('sub-uuid', [])
|
const subgraph = createMockSubgraph('sub-uuid', [])
|
||||||
;(subgraph as any).rootGraph = rootGraph
|
;(subgraph as Subgraph & { rootGraph: LGraph }).rootGraph = rootGraph
|
||||||
|
|
||||||
expect(getRootGraph(subgraph)).toBe(rootGraph)
|
expect(getRootGraph(subgraph)).toBe(rootGraph)
|
||||||
})
|
})
|
||||||
@@ -662,9 +663,10 @@ describe('graphTraversalUtil', () => {
|
|||||||
const rootGraph = createMockGraph([])
|
const rootGraph = createMockGraph([])
|
||||||
const midSubgraph = createMockSubgraph('mid-uuid', [])
|
const midSubgraph = createMockSubgraph('mid-uuid', [])
|
||||||
const deepSubgraph = createMockSubgraph('deep-uuid', [])
|
const deepSubgraph = createMockSubgraph('deep-uuid', [])
|
||||||
|
;(midSubgraph as Subgraph & { rootGraph: LGraph }).rootGraph = rootGraph
|
||||||
;(midSubgraph as any).rootGraph = rootGraph
|
;(
|
||||||
;(deepSubgraph as any).rootGraph = midSubgraph
|
deepSubgraph as Subgraph & { rootGraph: LGraph | Subgraph }
|
||||||
|
).rootGraph = midSubgraph
|
||||||
|
|
||||||
expect(getRootGraph(deepSubgraph)).toBe(rootGraph)
|
expect(getRootGraph(deepSubgraph)).toBe(rootGraph)
|
||||||
})
|
})
|
||||||
@@ -726,7 +728,7 @@ describe('graphTraversalUtil', () => {
|
|||||||
const graph = createMockGraph(nodes)
|
const graph = createMockGraph(nodes)
|
||||||
|
|
||||||
forEachSubgraphNode(graph, subgraphId, (node) => {
|
forEachSubgraphNode(graph, subgraphId, (node) => {
|
||||||
;(node as any).title = 'Updated Title'
|
node.title = 'Updated Title'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(nodes[0]).toHaveProperty('title', 'Updated Title')
|
expect(nodes[0]).toHaveProperty('title', 'Updated Title')
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
import type { INodeOutputSlot } from '@/lib/litegraph/src/interfaces'
|
||||||
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||||
import type { SerialisedLLinkArray } from '@/lib/litegraph/src/LLink'
|
import type { SerialisedLLinkArray } from '@/lib/litegraph/src/LLink'
|
||||||
import type { LGraph, LGraphNode, LLink } from '@/lib/litegraph/src/litegraph'
|
import type { LGraph, LGraphNode, LLink } from '@/lib/litegraph/src/litegraph'
|
||||||
@@ -79,12 +80,12 @@ export function fixBadLinks(
|
|||||||
options: {
|
options: {
|
||||||
fix?: boolean
|
fix?: boolean
|
||||||
silent?: boolean
|
silent?: boolean
|
||||||
logger?: { log: (...args: any[]) => void }
|
logger?: { log: (...args: unknown[]) => void }
|
||||||
} = {}
|
} = {}
|
||||||
): BadLinksData {
|
): BadLinksData {
|
||||||
const { fix = false, silent = false, logger: _logger = console } = options
|
const { fix = false, silent = false, logger: _logger = console } = options
|
||||||
const logger = {
|
const logger = {
|
||||||
log: (...args: any[]) => {
|
log: (...args: unknown[]) => {
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
_logger.log(...args)
|
_logger.log(...args)
|
||||||
}
|
}
|
||||||
@@ -166,7 +167,9 @@ export function fixBadLinks(
|
|||||||
patchedNode['outputs']![slot]!['links'].push(linkId)
|
patchedNode['outputs']![slot]!['links'].push(linkId)
|
||||||
if (fix) {
|
if (fix) {
|
||||||
node.outputs = node.outputs || []
|
node.outputs = node.outputs || []
|
||||||
node.outputs[slot] = node.outputs[slot] || ({} as any)
|
node.outputs[slot] =
|
||||||
|
node.outputs[slot] ||
|
||||||
|
({} satisfies Partial<INodeOutputSlot> as INodeOutputSlot)
|
||||||
node.outputs[slot]!.links = node.outputs[slot]!.links || []
|
node.outputs[slot]!.links = node.outputs[slot]!.links || []
|
||||||
node.outputs[slot]!.links!.push(linkId)
|
node.outputs[slot]!.links!.push(linkId)
|
||||||
}
|
}
|
||||||
@@ -428,7 +431,7 @@ export function fixBadLinks(
|
|||||||
(l) =>
|
(l) =>
|
||||||
l &&
|
l &&
|
||||||
(l[0] === data.deletedLinks[i] ||
|
(l[0] === data.deletedLinks[i] ||
|
||||||
(l as any).id === data.deletedLinks[i])
|
('id' in l && l.id === data.deletedLinks[i]))
|
||||||
)
|
)
|
||||||
if (idx === -1) {
|
if (idx === -1) {
|
||||||
logger.log(`INDEX NOT FOUND for #${data.deletedLinks[i]}`)
|
logger.log(`INDEX NOT FOUND for #${data.deletedLinks[i]}`)
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ describe('migrateWidgetsValues', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const widgets: IWidget[] = [
|
const widgets = [
|
||||||
{ name: 'normalInput', type: 'number' },
|
{ name: 'normalInput', type: 'number' },
|
||||||
{ name: 'anotherNormal', type: 'number' }
|
{ name: 'anotherNormal', type: 'number' }
|
||||||
] as unknown as IWidget[]
|
] as Partial<IWidget>[] as IWidget[]
|
||||||
|
|
||||||
const widgetValues = [42, 'dummy value', 3.14]
|
const widgetValues = [42, 'dummy value', 3.14]
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ describe('migrateWidgetsValues', () => {
|
|||||||
it('should handle empty widgets and values', () => {
|
it('should handle empty widgets and values', () => {
|
||||||
const inputDefs: Record<string, InputSpec> = {}
|
const inputDefs: Record<string, InputSpec> = {}
|
||||||
const widgets: IWidget[] = []
|
const widgets: IWidget[] = []
|
||||||
const widgetValues: any[] = []
|
const widgetValues: unknown[] = []
|
||||||
|
|
||||||
const result = migrateWidgetsValues(inputDefs, widgets, widgetValues)
|
const result = migrateWidgetsValues(inputDefs, widgets, widgetValues)
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
@@ -79,10 +79,10 @@ describe('migrateWidgetsValues', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const widgets: IWidget[] = [
|
const widgets = [
|
||||||
{ name: 'first', type: 'number' },
|
{ name: 'first', type: 'number' },
|
||||||
{ name: 'last', type: 'number' }
|
{ name: 'last', type: 'number' }
|
||||||
] as unknown as IWidget[]
|
] as Partial<IWidget>[] as IWidget[]
|
||||||
|
|
||||||
const widgetValues = ['first value', 'dummy', 'last value']
|
const widgetValues = ['first value', 'dummy', 'last value']
|
||||||
|
|
||||||
@@ -93,7 +93,8 @@ describe('migrateWidgetsValues', () => {
|
|||||||
|
|
||||||
describe('compressWidgetInputSlots', () => {
|
describe('compressWidgetInputSlots', () => {
|
||||||
it('should remove unconnected widget input slots', () => {
|
it('should remove unconnected widget input slots', () => {
|
||||||
const graph: ISerialisedGraph = {
|
// Using partial mock - only including properties needed for test
|
||||||
|
const graph = {
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -112,7 +113,7 @@ describe('compressWidgetInputSlots', () => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
links: [[2, 1, 0, 1, 0, 'INT']]
|
links: [[2, 1, 0, 1, 0, 'INT']]
|
||||||
} as unknown as ISerialisedGraph
|
} as Partial<ISerialisedGraph> as ISerialisedGraph
|
||||||
|
|
||||||
compressWidgetInputSlots(graph)
|
compressWidgetInputSlots(graph)
|
||||||
|
|
||||||
@@ -122,7 +123,7 @@ describe('compressWidgetInputSlots', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should update link target slots correctly', () => {
|
it('should update link target slots correctly', () => {
|
||||||
const graph: ISerialisedGraph = {
|
const graph = {
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -144,7 +145,7 @@ describe('compressWidgetInputSlots', () => {
|
|||||||
[2, 1, 0, 1, 1, 'INT'],
|
[2, 1, 0, 1, 1, 'INT'],
|
||||||
[3, 1, 0, 1, 2, 'INT']
|
[3, 1, 0, 1, 2, 'INT']
|
||||||
]
|
]
|
||||||
} as unknown as ISerialisedGraph
|
} as Partial<ISerialisedGraph> as ISerialisedGraph
|
||||||
|
|
||||||
compressWidgetInputSlots(graph)
|
compressWidgetInputSlots(graph)
|
||||||
|
|
||||||
@@ -160,10 +161,11 @@ describe('compressWidgetInputSlots', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle graphs with no nodes gracefully', () => {
|
it('should handle graphs with no nodes gracefully', () => {
|
||||||
const graph: ISerialisedGraph = {
|
// Using partial mock - only including properties needed for test
|
||||||
|
const graph = {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
links: []
|
links: []
|
||||||
} as unknown as ISerialisedGraph
|
} as Partial<ISerialisedGraph> as ISerialisedGraph
|
||||||
|
|
||||||
compressWidgetInputSlots(graph)
|
compressWidgetInputSlots(graph)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { api } from '@/scripts/api'
|
import { api } from '@/scripts/api'
|
||||||
|
import type { UserDataFullInfo } from '@/schemas/apiSchema'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync entities from the API to the entityByPath map.
|
* Sync entities from the API to the entityByPath map.
|
||||||
@@ -11,8 +12,8 @@ import { api } from '@/scripts/api'
|
|||||||
export async function syncEntities<T>(
|
export async function syncEntities<T>(
|
||||||
dir: string,
|
dir: string,
|
||||||
entityByPath: Record<string, T>,
|
entityByPath: Record<string, T>,
|
||||||
createEntity: (file: any) => T,
|
createEntity: (file: UserDataFullInfo & { path: string }) => T,
|
||||||
updateEntity: (entity: T, file: any) => void,
|
updateEntity: (entity: T, file: UserDataFullInfo & { path: string }) => void,
|
||||||
exclude: (file: T) => boolean = () => false
|
exclude: (file: T) => boolean = () => false
|
||||||
) {
|
) {
|
||||||
const files = (await api.listUserDataFullInfo(dir)).map((file) => ({
|
const files = (await api.listUserDataFullInfo(dir)).map((file) => ({
|
||||||
|
|||||||
@@ -1,43 +1,42 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||||
import { isSubgraphIoNode } from '@/utils/typeGuardUtil'
|
import { isSubgraphIoNode } from '@/utils/typeGuardUtil'
|
||||||
|
|
||||||
|
type NodeConstructor = { comfyClass?: string }
|
||||||
|
|
||||||
|
function createMockNode(nodeConstructor?: NodeConstructor): LGraphNode {
|
||||||
|
return { constructor: nodeConstructor } as Partial<LGraphNode> as LGraphNode
|
||||||
|
}
|
||||||
|
|
||||||
describe('typeGuardUtil', () => {
|
describe('typeGuardUtil', () => {
|
||||||
describe('isSubgraphIoNode', () => {
|
describe('isSubgraphIoNode', () => {
|
||||||
it('should identify SubgraphInputNode as IO node', () => {
|
it('should identify SubgraphInputNode as IO node', () => {
|
||||||
const node = {
|
const node = createMockNode({ comfyClass: 'SubgraphInputNode' })
|
||||||
constructor: { comfyClass: 'SubgraphInputNode' }
|
|
||||||
} as any
|
|
||||||
|
|
||||||
expect(isSubgraphIoNode(node)).toBe(true)
|
expect(isSubgraphIoNode(node)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should identify SubgraphOutputNode as IO node', () => {
|
it('should identify SubgraphOutputNode as IO node', () => {
|
||||||
const node = {
|
const node = createMockNode({ comfyClass: 'SubgraphOutputNode' })
|
||||||
constructor: { comfyClass: 'SubgraphOutputNode' }
|
|
||||||
} as any
|
|
||||||
|
|
||||||
expect(isSubgraphIoNode(node)).toBe(true)
|
expect(isSubgraphIoNode(node)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not identify regular nodes as IO nodes', () => {
|
it('should not identify regular nodes as IO nodes', () => {
|
||||||
const node = {
|
const node = createMockNode({ comfyClass: 'CLIPTextEncode' })
|
||||||
constructor: { comfyClass: 'CLIPTextEncode' }
|
|
||||||
} as any
|
|
||||||
|
|
||||||
expect(isSubgraphIoNode(node)).toBe(false)
|
expect(isSubgraphIoNode(node)).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle nodes without constructor', () => {
|
it('should handle nodes without constructor', () => {
|
||||||
const node = {} as any
|
const node = createMockNode(undefined)
|
||||||
|
|
||||||
expect(isSubgraphIoNode(node)).toBe(false)
|
expect(isSubgraphIoNode(node)).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle nodes without comfyClass', () => {
|
it('should handle nodes without comfyClass', () => {
|
||||||
const node = {
|
const node = createMockNode({})
|
||||||
constructor: {}
|
|
||||||
} as any
|
|
||||||
|
|
||||||
expect(isSubgraphIoNode(node)).toBe(false)
|
expect(isSubgraphIoNode(node)).toBe(false)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,17 +2,22 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|||||||
import { nextTick, ref } from 'vue'
|
import { nextTick, ref } from 'vue'
|
||||||
|
|
||||||
import type { LGraphNode, LGraph } from '@/lib/litegraph/src/litegraph'
|
import type { LGraphNode, LGraph } from '@/lib/litegraph/src/litegraph'
|
||||||
|
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
|
||||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||||
import { collectAllNodes } from '@/utils/graphTraversalUtil'
|
import { collectAllNodes } from '@/utils/graphTraversalUtil'
|
||||||
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
|
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
|
||||||
import { useWorkflowPacks } from '@/workbench/extensions/manager/composables/nodePack/useWorkflowPacks'
|
import { useWorkflowPacks } from '@/workbench/extensions/manager/composables/nodePack/useWorkflowPacks'
|
||||||
|
import type { WorkflowPack } from '@/workbench/extensions/manager/composables/nodePack/useWorkflowPacks'
|
||||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||||
|
import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils'
|
||||||
|
|
||||||
vi.mock('@vueuse/core', async () => {
|
vi.mock('@vueuse/core', async () => {
|
||||||
const actual = await vi.importActual('@vueuse/core')
|
const actual = await vi.importActual('@vueuse/core')
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
createSharedComposable: <Fn extends (...args: any[]) => any>(fn: Fn) => fn
|
createSharedComposable: <Fn extends (...args: unknown[]) => unknown>(
|
||||||
|
fn: Fn
|
||||||
|
) => fn
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -81,11 +86,11 @@ describe('useMissingNodes', () => {
|
|||||||
// Default setup: pack-3 is installed, others are not
|
// Default setup: pack-3 is installed, others are not
|
||||||
mockIsPackInstalled.mockImplementation((id: string) => id === 'pack-3')
|
mockIsPackInstalled.mockImplementation((id: string) => id === 'pack-3')
|
||||||
|
|
||||||
// @ts-expect-error - Mocking partial ComfyManagerStore for testing.
|
|
||||||
// We only need isPackInstalled method for these tests.
|
|
||||||
mockUseComfyManagerStore.mockReturnValue({
|
mockUseComfyManagerStore.mockReturnValue({
|
||||||
isPackInstalled: mockIsPackInstalled
|
isPackInstalled: mockIsPackInstalled
|
||||||
})
|
} as Partial<ReturnType<typeof useComfyManagerStore>> as ReturnType<
|
||||||
|
typeof useComfyManagerStore
|
||||||
|
>)
|
||||||
|
|
||||||
mockUseWorkflowPacks.mockReturnValue({
|
mockUseWorkflowPacks.mockReturnValue({
|
||||||
workflowPacks: ref([]),
|
workflowPacks: ref([]),
|
||||||
@@ -97,11 +102,11 @@ describe('useMissingNodes', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Reset node def store mock
|
// Reset node def store mock
|
||||||
// @ts-expect-error - Mocking partial NodeDefStore for testing.
|
|
||||||
// We only need nodeDefsByName for these tests.
|
|
||||||
mockUseNodeDefStore.mockReturnValue({
|
mockUseNodeDefStore.mockReturnValue({
|
||||||
nodeDefsByName: {}
|
nodeDefsByName: {}
|
||||||
})
|
} as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
|
||||||
|
typeof useNodeDefStore
|
||||||
|
>)
|
||||||
|
|
||||||
// Reset app.rootGraph.nodes
|
// Reset app.rootGraph.nodes
|
||||||
mockApp.rootGraph = { nodes: [] }
|
mockApp.rootGraph = { nodes: [] }
|
||||||
@@ -249,7 +254,7 @@ describe('useMissingNodes', () => {
|
|||||||
|
|
||||||
describe('reactivity', () => {
|
describe('reactivity', () => {
|
||||||
it('updates when workflow packs change', async () => {
|
it('updates when workflow packs change', async () => {
|
||||||
const workflowPacksRef = ref([])
|
const workflowPacksRef = ref<WorkflowPack[]>([])
|
||||||
mockUseWorkflowPacks.mockReturnValue({
|
mockUseWorkflowPacks.mockReturnValue({
|
||||||
workflowPacks: workflowPacksRef,
|
workflowPacks: workflowPacksRef,
|
||||||
isLoading: ref(false),
|
isLoading: ref(false),
|
||||||
@@ -265,8 +270,8 @@ describe('useMissingNodes', () => {
|
|||||||
expect(missingNodePacks.value).toEqual([])
|
expect(missingNodePacks.value).toEqual([])
|
||||||
|
|
||||||
// Update workflow packs
|
// Update workflow packs
|
||||||
// @ts-expect-error - mockWorkflowPacks is a simplified version without full WorkflowPack interface.
|
|
||||||
workflowPacksRef.value = mockWorkflowPacks
|
workflowPacksRef.value = mockWorkflowPacks as unknown as WorkflowPack[]
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
// Should update missing packs (2 missing since pack-3 is installed)
|
// Should update missing packs (2 missing since pack-3 is installed)
|
||||||
@@ -302,7 +307,7 @@ describe('useMissingNodes', () => {
|
|||||||
|
|
||||||
describe('missing core nodes detection', () => {
|
describe('missing core nodes detection', () => {
|
||||||
const createMockNode = (type: string, packId?: string, version?: string) =>
|
const createMockNode = (type: string, packId?: string, version?: string) =>
|
||||||
({
|
createMockLGraphNode({
|
||||||
type,
|
type,
|
||||||
properties: { cnr_id: packId, ver: version },
|
properties: { cnr_id: packId, ver: version },
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -314,7 +319,7 @@ describe('useMissingNodes', () => {
|
|||||||
mode: 0,
|
mode: 0,
|
||||||
inputs: [],
|
inputs: [],
|
||||||
outputs: []
|
outputs: []
|
||||||
}) as unknown as LGraphNode
|
})
|
||||||
|
|
||||||
it('identifies missing core nodes not in nodeDefStore', () => {
|
it('identifies missing core nodes not in nodeDefStore', () => {
|
||||||
const coreNode1 = createMockNode('CoreNode1', 'comfy-core', '1.2.0')
|
const coreNode1 = createMockNode('CoreNode1', 'comfy-core', '1.2.0')
|
||||||
@@ -323,13 +328,16 @@ describe('useMissingNodes', () => {
|
|||||||
// Mock collectAllNodes to return only the filtered nodes (missing core nodes)
|
// Mock collectAllNodes to return only the filtered nodes (missing core nodes)
|
||||||
mockCollectAllNodes.mockReturnValue([coreNode1, coreNode2])
|
mockCollectAllNodes.mockReturnValue([coreNode1, coreNode2])
|
||||||
|
|
||||||
|
const namedNode = {
|
||||||
|
name: 'RegisteredNode'
|
||||||
|
} as Partial<ComfyNodeDefImpl> as ComfyNodeDefImpl
|
||||||
mockUseNodeDefStore.mockReturnValue({
|
mockUseNodeDefStore.mockReturnValue({
|
||||||
nodeDefsByName: {
|
nodeDefsByName: {
|
||||||
// @ts-expect-error - Creating minimal mock of ComfyNodeDefImpl for testing.
|
RegisteredNode: namedNode
|
||||||
// Only including required properties for our test assertions.
|
|
||||||
RegisteredNode: { name: 'RegisteredNode' }
|
|
||||||
}
|
}
|
||||||
})
|
} as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
|
||||||
|
typeof useNodeDefStore
|
||||||
|
>)
|
||||||
|
|
||||||
const { missingCoreNodes } = useMissingNodes()
|
const { missingCoreNodes } = useMissingNodes()
|
||||||
|
|
||||||
@@ -347,10 +355,11 @@ describe('useMissingNodes', () => {
|
|||||||
// Mock collectAllNodes to return these nodes
|
// Mock collectAllNodes to return these nodes
|
||||||
mockCollectAllNodes.mockReturnValue([node120, node130, nodeNoVer])
|
mockCollectAllNodes.mockReturnValue([node120, node130, nodeNoVer])
|
||||||
|
|
||||||
// @ts-expect-error - Mocking partial NodeDefStore for testing.
|
|
||||||
mockUseNodeDefStore.mockReturnValue({
|
mockUseNodeDefStore.mockReturnValue({
|
||||||
nodeDefsByName: {}
|
nodeDefsByName: {}
|
||||||
})
|
} as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
|
||||||
|
typeof useNodeDefStore
|
||||||
|
>)
|
||||||
|
|
||||||
const { missingCoreNodes } = useMissingNodes()
|
const { missingCoreNodes } = useMissingNodes()
|
||||||
|
|
||||||
@@ -366,10 +375,11 @@ describe('useMissingNodes', () => {
|
|||||||
// Mock collectAllNodes to return only the filtered nodes (core nodes only)
|
// Mock collectAllNodes to return only the filtered nodes (core nodes only)
|
||||||
mockCollectAllNodes.mockReturnValue([coreNode])
|
mockCollectAllNodes.mockReturnValue([coreNode])
|
||||||
|
|
||||||
// @ts-expect-error - Mocking partial NodeDefStore for testing.
|
|
||||||
mockUseNodeDefStore.mockReturnValue({
|
mockUseNodeDefStore.mockReturnValue({
|
||||||
nodeDefsByName: {}
|
nodeDefsByName: {}
|
||||||
})
|
} as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
|
||||||
|
typeof useNodeDefStore
|
||||||
|
>)
|
||||||
|
|
||||||
const { missingCoreNodes } = useMissingNodes()
|
const { missingCoreNodes } = useMissingNodes()
|
||||||
|
|
||||||
@@ -384,13 +394,16 @@ describe('useMissingNodes', () => {
|
|||||||
|
|
||||||
mockUseNodeDefStore.mockReturnValue({
|
mockUseNodeDefStore.mockReturnValue({
|
||||||
nodeDefsByName: {
|
nodeDefsByName: {
|
||||||
// @ts-expect-error - Creating minimal mock of ComfyNodeDefImpl for testing.
|
RegisteredNode1: {
|
||||||
// Only including required properties for our test assertions.
|
name: 'RegisteredNode1'
|
||||||
RegisteredNode1: { name: 'RegisteredNode1' },
|
} as Partial<ComfyNodeDefImpl> as ComfyNodeDefImpl,
|
||||||
// @ts-expect-error - Creating minimal mock of ComfyNodeDefImpl for testing.
|
RegisteredNode2: {
|
||||||
RegisteredNode2: { name: 'RegisteredNode2' }
|
name: 'RegisteredNode2'
|
||||||
|
} as Partial<ComfyNodeDefImpl> as ComfyNodeDefImpl
|
||||||
}
|
}
|
||||||
})
|
} as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
|
||||||
|
typeof useNodeDefStore
|
||||||
|
>)
|
||||||
|
|
||||||
const { missingCoreNodes } = useMissingNodes()
|
const { missingCoreNodes } = useMissingNodes()
|
||||||
|
|
||||||
@@ -404,9 +417,7 @@ describe('useMissingNodes', () => {
|
|||||||
packId?: string,
|
packId?: string,
|
||||||
version?: string
|
version?: string
|
||||||
): LGraphNode =>
|
): LGraphNode =>
|
||||||
// @ts-expect-error - Creating a partial mock of LGraphNode for testing.
|
createMockLGraphNode({
|
||||||
// We only need specific properties for our tests, not the full LGraphNode interface.
|
|
||||||
({
|
|
||||||
type,
|
type,
|
||||||
properties: { cnr_id: packId, ver: version },
|
properties: { cnr_id: packId, ver: version },
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -441,10 +452,11 @@ describe('useMissingNodes', () => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
// Mock none of the nodes as registered
|
// Mock none of the nodes as registered
|
||||||
// @ts-expect-error - Mocking partial NodeDefStore for testing.
|
|
||||||
mockUseNodeDefStore.mockReturnValue({
|
mockUseNodeDefStore.mockReturnValue({
|
||||||
nodeDefsByName: {}
|
nodeDefsByName: {}
|
||||||
})
|
} as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
|
||||||
|
typeof useNodeDefStore
|
||||||
|
>)
|
||||||
|
|
||||||
const { missingCoreNodes } = useMissingNodes()
|
const { missingCoreNodes } = useMissingNodes()
|
||||||
|
|
||||||
@@ -482,10 +494,13 @@ describe('useMissingNodes', () => {
|
|||||||
|
|
||||||
mockUseNodeDefStore.mockReturnValue({
|
mockUseNodeDefStore.mockReturnValue({
|
||||||
nodeDefsByName: {
|
nodeDefsByName: {
|
||||||
// @ts-expect-error - Creating minimal mock of ComfyNodeDefImpl for testing.
|
RegisteredCore: {
|
||||||
RegisteredCore: { name: 'RegisteredCore' }
|
name: 'RegisteredCore'
|
||||||
|
} as Partial<ComfyNodeDefImpl> as ComfyNodeDefImpl
|
||||||
}
|
}
|
||||||
})
|
} as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
|
||||||
|
typeof useNodeDefStore
|
||||||
|
>)
|
||||||
|
|
||||||
let capturedFilterFunction: ((node: LGraphNode) => boolean) | undefined
|
let capturedFilterFunction: ((node: LGraphNode) => boolean) | undefined
|
||||||
|
|
||||||
@@ -561,12 +576,12 @@ describe('useMissingNodes', () => {
|
|||||||
nodes: [subgraphMissingNode, subgraphRegisteredNode]
|
nodes: [subgraphMissingNode, subgraphRegisteredNode]
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockSubgraphNode = {
|
const mockSubgraphNode = createMockLGraphNode({
|
||||||
isSubgraphNode: () => true,
|
isSubgraphNode: () => true,
|
||||||
subgraph: mockSubgraph,
|
subgraph: mockSubgraph,
|
||||||
type: 'SubgraphContainer',
|
type: 'SubgraphContainer',
|
||||||
properties: { cnr_id: 'custom-pack' }
|
properties: { cnr_id: 'custom-pack' }
|
||||||
} as unknown as LGraphNode
|
})
|
||||||
|
|
||||||
const mockMainGraph = {
|
const mockMainGraph = {
|
||||||
nodes: [mainMissingNode, mockSubgraphNode]
|
nodes: [mainMissingNode, mockSubgraphNode]
|
||||||
@@ -576,10 +591,13 @@ describe('useMissingNodes', () => {
|
|||||||
|
|
||||||
mockUseNodeDefStore.mockReturnValue({
|
mockUseNodeDefStore.mockReturnValue({
|
||||||
nodeDefsByName: {
|
nodeDefsByName: {
|
||||||
// @ts-expect-error - Creating minimal mock of ComfyNodeDefImpl for testing.
|
SubgraphRegistered: {
|
||||||
SubgraphRegistered: { name: 'SubgraphRegistered' }
|
name: 'SubgraphRegistered'
|
||||||
|
} as Partial<ComfyNodeDefImpl> as ComfyNodeDefImpl
|
||||||
}
|
}
|
||||||
})
|
} as Partial<ReturnType<typeof useNodeDefStore>> as ReturnType<
|
||||||
|
typeof useNodeDefStore
|
||||||
|
>)
|
||||||
|
|
||||||
const { missingCoreNodes } = useMissingNodes()
|
const { missingCoreNodes } = useMissingNodes()
|
||||||
|
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ describe('usePacksSelection', () => {
|
|||||||
describe('edge cases', () => {
|
describe('edge cases', () => {
|
||||||
it('should handle packs with undefined ids', () => {
|
it('should handle packs with undefined ids', () => {
|
||||||
const nodePacks = ref<NodePack[]>([
|
const nodePacks = ref<NodePack[]>([
|
||||||
{ ...createMockPack('pack1'), id: undefined as any },
|
{ ...createMockPack('pack1'), id: undefined },
|
||||||
createMockPack('pack2')
|
createMockPack('pack2')
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ describe('usePacksStatus', () => {
|
|||||||
|
|
||||||
it('should handle packs without ids', () => {
|
it('should handle packs without ids', () => {
|
||||||
const nodePacks = ref<NodePack[]>([
|
const nodePacks = ref<NodePack[]>([
|
||||||
{ ...createMockPack('pack1'), id: undefined as any },
|
{ ...createMockPack('pack1'), id: undefined },
|
||||||
createMockPack('pack2')
|
createMockPack('pack2')
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import type { components } from '@/types/comfyRegistryTypes'
|
|||||||
import { mapAllNodes } from '@/utils/graphTraversalUtil'
|
import { mapAllNodes } from '@/utils/graphTraversalUtil'
|
||||||
import { useNodePacks } from '@/workbench/extensions/manager/composables/nodePack/useNodePacks'
|
import { useNodePacks } from '@/workbench/extensions/manager/composables/nodePack/useNodePacks'
|
||||||
|
|
||||||
type WorkflowPack = {
|
export type WorkflowPack = {
|
||||||
id:
|
id:
|
||||||
| ComfyWorkflowJSON['nodes'][number]['properties']['cnr_id']
|
| ComfyWorkflowJSON['nodes'][number]['properties']['cnr_id']
|
||||||
| ComfyWorkflowJSON['nodes'][number]['properties']['aux_id']
|
| ComfyWorkflowJSON['nodes'][number]['properties']['aux_id']
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const STORAGE_KEYS = {
|
|||||||
/**
|
/**
|
||||||
* Interface for conflict acknowledgment state
|
* Interface for conflict acknowledgment state
|
||||||
*/
|
*/
|
||||||
interface ConflictAcknowledgmentState {
|
export interface ConflictAcknowledgmentState {
|
||||||
modal_dismissed: boolean
|
modal_dismissed: boolean
|
||||||
red_dot_dismissed: boolean
|
red_dot_dismissed: boolean
|
||||||
warning_banner_dismissed: boolean
|
warning_banner_dismissed: boolean
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
|||||||
import type { components } from '@/types/comfyRegistryTypes'
|
import type { components } from '@/types/comfyRegistryTypes'
|
||||||
import { useInstalledPacks } from '@/workbench/extensions/manager/composables/nodePack/useInstalledPacks'
|
import { useInstalledPacks } from '@/workbench/extensions/manager/composables/nodePack/useInstalledPacks'
|
||||||
import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
|
import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
|
||||||
|
import type { ConflictAcknowledgmentState } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
|
||||||
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
||||||
import { useComfyManagerService } from '@/workbench/extensions/manager/services/comfyManagerService'
|
import { useComfyManagerService } from '@/workbench/extensions/manager/services/comfyManagerService'
|
||||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||||
@@ -121,13 +122,17 @@ describe('useConflictDetection', () => {
|
|||||||
getImportFailInfoBulk: vi.fn(),
|
getImportFailInfoBulk: vi.fn(),
|
||||||
isLoading: ref(false),
|
isLoading: ref(false),
|
||||||
error: ref<string | null>(null)
|
error: ref<string | null>(null)
|
||||||
} as unknown as ReturnType<typeof useComfyManagerService>
|
} as Partial<ReturnType<typeof useComfyManagerService>> as ReturnType<
|
||||||
|
typeof useComfyManagerService
|
||||||
|
>
|
||||||
|
|
||||||
const mockRegistryService = {
|
const mockRegistryService = {
|
||||||
getBulkNodeVersions: vi.fn(),
|
getBulkNodeVersions: vi.fn(),
|
||||||
isLoading: ref(false),
|
isLoading: ref(false),
|
||||||
error: ref<string | null>(null)
|
error: ref<string | null>(null)
|
||||||
} as unknown as ReturnType<typeof useComfyRegistryService>
|
} as Partial<ReturnType<typeof useComfyRegistryService>> as ReturnType<
|
||||||
|
typeof useComfyRegistryService
|
||||||
|
>
|
||||||
|
|
||||||
// Create a ref that can be modified in tests
|
// Create a ref that can be modified in tests
|
||||||
const mockInstalledPacksWithVersions = ref<{ id: string; version: string }[]>(
|
const mockInstalledPacksWithVersions = ref<{ id: string; version: string }[]>(
|
||||||
@@ -143,35 +148,43 @@ describe('useConflictDetection', () => {
|
|||||||
isReady: ref(false),
|
isReady: ref(false),
|
||||||
isLoading: ref(false),
|
isLoading: ref(false),
|
||||||
error: ref<unknown>(null)
|
error: ref<unknown>(null)
|
||||||
} as unknown as ReturnType<typeof useInstalledPacks>
|
} as Partial<ReturnType<typeof useInstalledPacks>> as ReturnType<
|
||||||
|
typeof useInstalledPacks
|
||||||
|
>
|
||||||
|
|
||||||
const mockManagerStore = {
|
const mockManagerStore = {
|
||||||
isPackEnabled: vi.fn()
|
isPackEnabled: vi.fn()
|
||||||
} as unknown as ReturnType<typeof useComfyManagerStore>
|
} as Partial<ReturnType<typeof useComfyManagerStore>> as ReturnType<
|
||||||
|
typeof useComfyManagerStore
|
||||||
|
>
|
||||||
|
|
||||||
// Create refs that can be used to control computed properties
|
// Create refs that can be used to control computed properties
|
||||||
const mockConflictedPackages = ref<ConflictDetectionResult[]>([])
|
let mockConflictedPackages: ConflictDetectionResult[] = []
|
||||||
|
|
||||||
const mockConflictStore = {
|
const mockConflictStore = {
|
||||||
hasConflicts: computed(() =>
|
get hasConflicts() {
|
||||||
mockConflictedPackages.value.some((p) => p.has_conflict)
|
return mockConflictedPackages.some((p) => p.has_conflict)
|
||||||
),
|
},
|
||||||
conflictedPackages: mockConflictedPackages,
|
get conflictedPackages() {
|
||||||
bannedPackages: computed(() =>
|
return mockConflictedPackages
|
||||||
mockConflictedPackages.value.filter((p) =>
|
},
|
||||||
|
get bannedPackages() {
|
||||||
|
return mockConflictedPackages.filter((p) =>
|
||||||
p.conflicts?.some((c) => c.type === 'banned')
|
p.conflicts?.some((c) => c.type === 'banned')
|
||||||
)
|
)
|
||||||
),
|
},
|
||||||
securityPendingPackages: computed(() =>
|
get securityPendingPackages() {
|
||||||
mockConflictedPackages.value.filter((p) =>
|
return mockConflictedPackages.filter((p) =>
|
||||||
p.conflicts?.some((c) => c.type === 'pending')
|
p.conflicts?.some((c) => c.type === 'pending')
|
||||||
)
|
)
|
||||||
),
|
},
|
||||||
setConflictedPackages: vi.fn(),
|
setConflictedPackages: vi.fn(),
|
||||||
clearConflicts: vi.fn()
|
clearConflicts: vi.fn()
|
||||||
} as unknown as ReturnType<typeof useConflictDetectionStore>
|
} as Partial<ReturnType<typeof useConflictDetectionStore>> as ReturnType<
|
||||||
|
typeof useConflictDetectionStore
|
||||||
|
>
|
||||||
|
|
||||||
const mockIsInitialized = ref(true)
|
const mockIsInitialized = true
|
||||||
const mockSystemStatsStore = {
|
const mockSystemStatsStore = {
|
||||||
systemStats: {
|
systemStats: {
|
||||||
system: {
|
system: {
|
||||||
@@ -199,26 +212,26 @@ describe('useConflictDetection', () => {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
isInitialized: mockIsInitialized,
|
isInitialized: mockIsInitialized,
|
||||||
$state: {} as never,
|
|
||||||
$patch: vi.fn(),
|
|
||||||
$reset: vi.fn(),
|
|
||||||
$subscribe: vi.fn(),
|
|
||||||
$onAction: vi.fn(),
|
|
||||||
$dispose: vi.fn(),
|
|
||||||
$id: 'systemStats',
|
|
||||||
_customProperties: new Set<string>()
|
_customProperties: new Set<string>()
|
||||||
} as unknown as ReturnType<typeof useSystemStatsStore>
|
} as Partial<ReturnType<typeof useSystemStatsStore>> as ReturnType<
|
||||||
|
typeof useSystemStatsStore
|
||||||
|
>
|
||||||
|
|
||||||
const mockAcknowledgment = {
|
const mockAcknowledgment = {
|
||||||
checkComfyUIVersionChange: vi.fn(),
|
checkComfyUIVersionChange: vi.fn(),
|
||||||
acknowledgmentState: computed(() => ({})),
|
acknowledgmentState: computed(
|
||||||
|
() => ({}) as Partial<ConflictAcknowledgmentState>
|
||||||
|
),
|
||||||
shouldShowConflictModal: computed(() => false),
|
shouldShowConflictModal: computed(() => false),
|
||||||
shouldShowRedDot: computed(() => false),
|
shouldShowRedDot: computed(() => false),
|
||||||
shouldShowManagerBanner: computed(() => false),
|
shouldShowManagerBanner: computed(() => false),
|
||||||
dismissRedDotNotification: vi.fn(),
|
dismissRedDotNotification: vi.fn(),
|
||||||
dismissWarningBanner: vi.fn(),
|
dismissWarningBanner: vi.fn(),
|
||||||
markConflictsAsSeen: vi.fn()
|
markConflictsAsSeen: vi.fn()
|
||||||
} as unknown as ReturnType<typeof useConflictAcknowledgment>
|
} as Partial<ReturnType<typeof useConflictAcknowledgment>> as ReturnType<
|
||||||
|
typeof useConflictAcknowledgment
|
||||||
|
>
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
@@ -249,7 +262,7 @@ describe('useConflictDetection', () => {
|
|||||||
// Reset the installedPacksWithVersions data
|
// Reset the installedPacksWithVersions data
|
||||||
mockInstalledPacksWithVersions.value = []
|
mockInstalledPacksWithVersions.value = []
|
||||||
// Reset conflicted packages
|
// Reset conflicted packages
|
||||||
mockConflictedPackages.value = []
|
mockConflictedPackages = []
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -414,7 +427,7 @@ describe('useConflictDetection', () => {
|
|||||||
error: 'Import error',
|
error: 'Import error',
|
||||||
name: 'fail-pack',
|
name: 'fail-pack',
|
||||||
path: '/path/to/pack'
|
path: '/path/to/pack'
|
||||||
} as any // The actual API returns different structure than types
|
} as { error?: string; traceback?: string } | null // The actual API returns different structure than types
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mock registry response for the package
|
// Mock registry response for the package
|
||||||
@@ -437,7 +450,7 @@ describe('useConflictDetection', () => {
|
|||||||
|
|
||||||
describe('computed properties', () => {
|
describe('computed properties', () => {
|
||||||
it('should expose conflict status from store', () => {
|
it('should expose conflict status from store', () => {
|
||||||
mockConflictedPackages.value = [
|
mockConflictedPackages = [
|
||||||
{
|
{
|
||||||
package_id: 'test',
|
package_id: 'test',
|
||||||
package_name: 'Test',
|
package_name: 'Test',
|
||||||
@@ -450,8 +463,8 @@ describe('useConflictDetection', () => {
|
|||||||
useConflictDetection()
|
useConflictDetection()
|
||||||
|
|
||||||
// The hasConflicts computed should be true since we have a conflict
|
// The hasConflicts computed should be true since we have a conflict
|
||||||
expect(mockConflictedPackages.value).toHaveLength(1)
|
expect(mockConflictedPackages).toHaveLength(1)
|
||||||
expect(mockConflictedPackages.value[0].has_conflict).toBe(true)
|
expect(mockConflictedPackages[0].has_conflict).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import { useManagerQueue } from '@/workbench/extensions/manager/composables/useManagerQueue'
|
import { useManagerQueue } from '@/workbench/extensions/manager/composables/useManagerQueue'
|
||||||
@@ -22,9 +23,11 @@ type ManagerTaskHistory = Record<
|
|||||||
type ManagerTaskQueue = components['schemas']['TaskStateMessage']
|
type ManagerTaskQueue = components['schemas']['TaskStateMessage']
|
||||||
|
|
||||||
describe('useManagerQueue', () => {
|
describe('useManagerQueue', () => {
|
||||||
let taskHistory: any
|
let taskHistory: Ref<ManagerTaskHistory>
|
||||||
let taskQueue: any
|
let taskQueue: Ref<ManagerTaskQueue>
|
||||||
let installedPacks: any
|
let installedPacks: Ref<
|
||||||
|
Record<string, components['schemas']['ManagerPackInstalled']>
|
||||||
|
>
|
||||||
|
|
||||||
const createManagerQueue = () => {
|
const createManagerQueue = () => {
|
||||||
taskHistory = ref<ManagerTaskHistory>({})
|
taskHistory = ref<ManagerTaskHistory>({})
|
||||||
@@ -67,14 +70,28 @@ describe('useManagerQueue', () => {
|
|||||||
{
|
{
|
||||||
ui_id: 'task1',
|
ui_id: 'task1',
|
||||||
client_id: 'test-client-id',
|
client_id: 'test-client-id',
|
||||||
task_name: 'Installing pack1'
|
kind: 'install',
|
||||||
|
params: {
|
||||||
|
id: 'pack1',
|
||||||
|
version: '1.0.0',
|
||||||
|
selected_version: '1.0.0',
|
||||||
|
mode: 'remote' as const,
|
||||||
|
channel: 'default' as const
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
taskQueue.value.pending_queue = [
|
taskQueue.value.pending_queue = [
|
||||||
{
|
{
|
||||||
ui_id: 'task2',
|
ui_id: 'task2',
|
||||||
client_id: 'test-client-id',
|
client_id: 'test-client-id',
|
||||||
task_name: 'Installing pack2'
|
kind: 'install',
|
||||||
|
params: {
|
||||||
|
id: 'pack2',
|
||||||
|
version: '1.0.0',
|
||||||
|
selected_version: '1.0.0',
|
||||||
|
mode: 'remote' as const,
|
||||||
|
channel: 'default' as const
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -101,12 +118,18 @@ describe('useManagerQueue', () => {
|
|||||||
task1: {
|
task1: {
|
||||||
ui_id: 'task1',
|
ui_id: 'task1',
|
||||||
client_id: 'test-client-id',
|
client_id: 'test-client-id',
|
||||||
status: { status_str: 'success', completed: true }
|
kind: 'install',
|
||||||
|
timestamp: '2024-01-01T00:00:00Z',
|
||||||
|
result: 'success',
|
||||||
|
status: { status_str: 'success', completed: true, messages: [] }
|
||||||
},
|
},
|
||||||
task2: {
|
task2: {
|
||||||
ui_id: 'task2',
|
ui_id: 'task2',
|
||||||
client_id: 'test-client-id',
|
client_id: 'test-client-id',
|
||||||
status: { status_str: 'success', completed: true }
|
kind: 'install',
|
||||||
|
timestamp: '2024-01-01T00:00:00Z',
|
||||||
|
result: 'success',
|
||||||
|
status: { status_str: 'success', completed: true, messages: [] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,12 +221,12 @@ describe('useManagerQueue', () => {
|
|||||||
it('handles empty installed_packs gracefully', () => {
|
it('handles empty installed_packs gracefully', () => {
|
||||||
const queue = createManagerQueue()
|
const queue = createManagerQueue()
|
||||||
|
|
||||||
const mockState: any = {
|
const mockState = {
|
||||||
history: {},
|
history: {},
|
||||||
running_queue: [],
|
running_queue: [],
|
||||||
pending_queue: [],
|
pending_queue: [],
|
||||||
installed_packs: undefined
|
installed_packs: undefined!
|
||||||
}
|
} satisfies Partial<ManagerTaskQueue> as ManagerTaskQueue
|
||||||
|
|
||||||
// Just call the function - if it throws, the test will fail automatically
|
// Just call the function - if it throws, the test will fail automatically
|
||||||
queue.updateTaskState(mockState)
|
queue.updateTaskState(mockState)
|
||||||
|
|||||||
@@ -1,45 +1,45 @@
|
|||||||
|
import { createTestingPinia } from '@pinia/testing'
|
||||||
|
import { setActivePinia } from 'pinia'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
|
||||||
import { api } from '@/scripts/api'
|
import { api } from '@/scripts/api'
|
||||||
import { useExtensionStore } from '@/stores/extensionStore'
|
|
||||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||||
import {
|
import {
|
||||||
ManagerUIState,
|
ManagerUIState,
|
||||||
useManagerState
|
useManagerState
|
||||||
} from '@/workbench/extensions/manager/composables/useManagerState'
|
} from '@/workbench/extensions/manager/composables/useManagerState'
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies that are not stores
|
||||||
vi.mock('@/scripts/api', () => ({
|
vi.mock('@/scripts/api', () => ({
|
||||||
api: {
|
api: {
|
||||||
getClientFeatureFlags: vi.fn(),
|
getClientFeatureFlags: vi.fn(),
|
||||||
getServerFeature: vi.fn()
|
getServerFeature: vi.fn(),
|
||||||
|
getSystemStats: vi.fn()
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/composables/useFeatureFlags', () => ({
|
vi.mock('@/composables/useFeatureFlags', () => {
|
||||||
useFeatureFlags: vi.fn(() => ({
|
const featureFlag = vi.fn()
|
||||||
flags: { supportsManagerV4: false },
|
return {
|
||||||
featureFlag: vi.fn()
|
useFeatureFlags: vi.fn(() => ({
|
||||||
}))
|
flags: { supportsManagerV4: false },
|
||||||
}))
|
featureFlag
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/stores/extensionStore', () => ({
|
vi.mock('@/services/dialogService', () => {
|
||||||
useExtensionStore: vi.fn()
|
const showManagerPopup = vi.fn()
|
||||||
}))
|
const showLegacyManagerPopup = vi.fn()
|
||||||
|
const showSettingsDialog = vi.fn()
|
||||||
vi.mock('@/stores/systemStatsStore', () => ({
|
return {
|
||||||
useSystemStatsStore: vi.fn()
|
useDialogService: vi.fn(() => ({
|
||||||
}))
|
showManagerPopup,
|
||||||
|
showLegacyManagerPopup,
|
||||||
vi.mock('@/services/dialogService', () => ({
|
showSettingsDialog
|
||||||
useDialogService: vi.fn(() => ({
|
}))
|
||||||
showManagerPopup: vi.fn(),
|
}
|
||||||
showLegacyManagerPopup: vi.fn(),
|
})
|
||||||
showSettingsDialog: vi.fn()
|
|
||||||
}))
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock('@/stores/commandStore', () => ({
|
vi.mock('@/stores/commandStore', () => ({
|
||||||
useCommandStore: vi.fn(() => ({
|
useCommandStore: vi.fn(() => ({
|
||||||
@@ -47,198 +47,223 @@ vi.mock('@/stores/commandStore', () => ({
|
|||||||
}))
|
}))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/stores/toastStore', () => ({
|
vi.mock('@/platform/updates/common/toastStore', () => {
|
||||||
useToastStore: vi.fn(() => ({
|
const add = vi.fn()
|
||||||
add: vi.fn()
|
return {
|
||||||
}))
|
useToastStore: vi.fn(() => ({
|
||||||
}))
|
add
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/workbench/extensions/manager/composables/useManagerDialog', () => ({
|
vi.mock('@/workbench/extensions/manager/composables/useManagerDialog', () => {
|
||||||
useManagerDialog: vi.fn(() => ({
|
const show = vi.fn()
|
||||||
show: vi.fn(),
|
const hide = vi.fn()
|
||||||
hide: vi.fn()
|
return {
|
||||||
}))
|
useManagerDialog: vi.fn(() => ({
|
||||||
}))
|
show,
|
||||||
|
hide
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
describe('useManagerState', () => {
|
describe('useManagerState', () => {
|
||||||
|
let systemStatsStore: ReturnType<typeof useSystemStatsStore>
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
// Create a fresh testing pinia and activate it for each test
|
||||||
|
setActivePinia(
|
||||||
|
createTestingPinia({
|
||||||
|
stubActions: false,
|
||||||
|
createSpy: vi.fn
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize stores
|
||||||
|
systemStatsStore = useSystemStatsStore()
|
||||||
|
|
||||||
|
// Reset all mocks
|
||||||
|
vi.resetAllMocks()
|
||||||
|
|
||||||
|
// Set default mock returns
|
||||||
|
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||||
|
vi.mocked(api.getServerFeature).mockReturnValue(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('managerUIState property', () => {
|
describe('managerUIState property', () => {
|
||||||
it('should return DISABLED state when --enable-manager is NOT present', () => {
|
it('should return DISABLED state when --enable-manager is NOT present', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref({
|
systemStatsStore.$patch({
|
||||||
system: { argv: ['python', 'main.py'] } // No --enable-manager flag
|
systemStats: {
|
||||||
}),
|
system: {
|
||||||
isInitialized: ref(true)
|
os: 'Test OS',
|
||||||
} as any)
|
python_version: '3.10',
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
embedded_python: false,
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
comfyui_version: '1.0.0',
|
||||||
extensions: []
|
pytorch_version: '2.0.0',
|
||||||
} as any)
|
argv: ['python', 'main.py'], // No --enable-manager flag
|
||||||
|
ram_total: 16000000000,
|
||||||
|
ram_free: 8000000000
|
||||||
|
},
|
||||||
|
devices: []
|
||||||
|
},
|
||||||
|
isInitialized: true
|
||||||
|
})
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
|
|
||||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.DISABLED)
|
expect(managerState.managerUIState.value).toBe(ManagerUIState.DISABLED)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return LEGACY_UI state when --enable-manager-legacy-ui is present', () => {
|
it('should return LEGACY_UI state when --enable-manager-legacy-ui is present', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref({
|
systemStatsStore.$patch({
|
||||||
|
systemStats: {
|
||||||
system: {
|
system: {
|
||||||
|
os: 'Test OS',
|
||||||
|
python_version: '3.10',
|
||||||
|
embedded_python: false,
|
||||||
|
comfyui_version: '1.0.0',
|
||||||
|
pytorch_version: '2.0.0',
|
||||||
argv: [
|
argv: [
|
||||||
'python',
|
'python',
|
||||||
'main.py',
|
'main.py',
|
||||||
'--enable-manager',
|
'--enable-manager',
|
||||||
'--enable-manager-legacy-ui'
|
'--enable-manager-legacy-ui'
|
||||||
]
|
],
|
||||||
} // Both flags needed
|
ram_total: 16000000000,
|
||||||
}),
|
ram_free: 8000000000
|
||||||
isInitialized: ref(true)
|
},
|
||||||
} as any)
|
devices: []
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
},
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
isInitialized: true
|
||||||
extensions: []
|
})
|
||||||
} as any)
|
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
|
|
||||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
|
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return NEW_UI state when client and server both support v4', () => {
|
it('should return NEW_UI state when client and server both support v4', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref({
|
systemStatsStore.$patch({
|
||||||
system: { argv: ['python', 'main.py', '--enable-manager'] }
|
systemStats: {
|
||||||
}), // Need --enable-manager
|
system: {
|
||||||
isInitialized: ref(true)
|
os: 'Test OS',
|
||||||
} as any)
|
python_version: '3.10',
|
||||||
|
embedded_python: false,
|
||||||
|
comfyui_version: '1.0.0',
|
||||||
|
pytorch_version: '2.0.0',
|
||||||
|
argv: ['python', 'main.py', '--enable-manager'],
|
||||||
|
ram_total: 16000000000,
|
||||||
|
ram_free: 8000000000
|
||||||
|
},
|
||||||
|
devices: []
|
||||||
|
},
|
||||||
|
isInitialized: true
|
||||||
|
})
|
||||||
|
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||||
supports_manager_v4_ui: true
|
supports_manager_v4_ui: true
|
||||||
})
|
})
|
||||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
|
||||||
flags: { supportsManagerV4: true },
|
|
||||||
featureFlag: vi.fn()
|
|
||||||
} as any)
|
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
|
||||||
extensions: []
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
|
|
||||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.NEW_UI)
|
expect(managerState.managerUIState.value).toBe(ManagerUIState.NEW_UI)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return LEGACY_UI state when server supports v4 but client does not', () => {
|
it('should return LEGACY_UI state when server supports v4 but client does not', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref({
|
systemStatsStore.$patch({
|
||||||
system: { argv: ['python', 'main.py', '--enable-manager'] }
|
systemStats: {
|
||||||
}), // Need --enable-manager
|
system: {
|
||||||
isInitialized: ref(true)
|
os: 'Test OS',
|
||||||
} as any)
|
python_version: '3.10',
|
||||||
|
embedded_python: false,
|
||||||
|
comfyui_version: '1.0.0',
|
||||||
|
pytorch_version: '2.0.0',
|
||||||
|
argv: ['python', 'main.py', '--enable-manager'],
|
||||||
|
ram_total: 16000000000,
|
||||||
|
ram_free: 8000000000
|
||||||
|
},
|
||||||
|
devices: []
|
||||||
|
},
|
||||||
|
isInitialized: true
|
||||||
|
})
|
||||||
|
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||||
supports_manager_v4_ui: false
|
supports_manager_v4_ui: false
|
||||||
})
|
})
|
||||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
|
||||||
flags: { supportsManagerV4: true },
|
|
||||||
featureFlag: vi.fn()
|
|
||||||
} as any)
|
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
|
||||||
extensions: []
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
|
|
||||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
|
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return LEGACY_UI state when legacy manager extension exists', () => {
|
it('should return LEGACY_UI state when server does not support v4', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref({
|
systemStatsStore.$patch({
|
||||||
system: { argv: ['python', 'main.py', '--enable-manager'] }
|
systemStats: {
|
||||||
}), // Need --enable-manager
|
system: {
|
||||||
isInitialized: ref(true)
|
os: 'Test OS',
|
||||||
} as any)
|
python_version: '3.10',
|
||||||
|
embedded_python: false,
|
||||||
|
comfyui_version: '1.0.0',
|
||||||
|
pytorch_version: '2.0.0',
|
||||||
|
argv: ['python', 'main.py', '--enable-manager'],
|
||||||
|
ram_total: 16000000000,
|
||||||
|
ram_free: 8000000000
|
||||||
|
},
|
||||||
|
devices: []
|
||||||
|
},
|
||||||
|
isInitialized: true
|
||||||
|
})
|
||||||
|
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
vi.mocked(api.getServerFeature).mockReturnValue(false)
|
||||||
flags: { supportsManagerV4: false },
|
|
||||||
featureFlag: vi.fn()
|
|
||||||
} as any)
|
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
|
||||||
extensions: [{ name: 'Comfy.CustomNodesManager' }]
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
|
|
||||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
|
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return NEW_UI state when server feature flags are undefined', () => {
|
it('should return NEW_UI state when server feature flags are undefined', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref({
|
systemStatsStore.$patch({
|
||||||
system: { argv: ['python', 'main.py', '--enable-manager'] }
|
systemStats: {
|
||||||
}), // Need --enable-manager
|
system: {
|
||||||
isInitialized: ref(true)
|
os: 'Test OS',
|
||||||
} as any)
|
python_version: '3.10',
|
||||||
|
embedded_python: false,
|
||||||
|
comfyui_version: '1.0.0',
|
||||||
|
pytorch_version: '2.0.0',
|
||||||
|
argv: ['python', 'main.py', '--enable-manager'],
|
||||||
|
ram_total: 16000000000,
|
||||||
|
ram_free: 8000000000
|
||||||
|
},
|
||||||
|
devices: []
|
||||||
|
},
|
||||||
|
isInitialized: true
|
||||||
|
})
|
||||||
|
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||||
vi.mocked(api.getServerFeature).mockReturnValue(undefined)
|
vi.mocked(api.getServerFeature).mockReturnValue(undefined)
|
||||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
|
||||||
flags: { supportsManagerV4: undefined },
|
|
||||||
featureFlag: vi.fn()
|
|
||||||
} as any)
|
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
|
||||||
extensions: []
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
|
// When server feature flags haven't loaded yet, default to NEW_UI
|
||||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.NEW_UI)
|
expect(managerState.managerUIState.value).toBe(ManagerUIState.NEW_UI)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return LEGACY_UI state when server does not support v4', () => {
|
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
|
||||||
systemStats: ref({
|
|
||||||
system: { argv: ['python', 'main.py', '--enable-manager'] }
|
|
||||||
}), // Need --enable-manager
|
|
||||||
isInitialized: ref(true)
|
|
||||||
} as any)
|
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
|
||||||
vi.mocked(api.getServerFeature).mockReturnValue(false)
|
|
||||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
|
||||||
flags: { supportsManagerV4: false },
|
|
||||||
featureFlag: vi.fn()
|
|
||||||
} as any)
|
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
|
||||||
extensions: []
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const managerState = useManagerState()
|
|
||||||
|
|
||||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle null systemStats gracefully', () => {
|
it('should handle null systemStats gracefully', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref(null),
|
systemStatsStore.$patch({
|
||||||
isInitialized: ref(true)
|
systemStats: null,
|
||||||
} as any)
|
isInitialized: true
|
||||||
|
})
|
||||||
|
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||||
supports_manager_v4_ui: true
|
supports_manager_v4_ui: true
|
||||||
})
|
})
|
||||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
|
||||||
flags: { supportsManagerV4: true },
|
|
||||||
featureFlag: vi.fn()
|
|
||||||
} as any)
|
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
|
||||||
extensions: []
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
|
|
||||||
// When systemStats is null, we can't check for --enable-manager flag, so manager is disabled
|
// When systemStats is null, we can't check for --enable-manager flag, so manager is disabled
|
||||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.DISABLED)
|
expect(managerState.managerUIState.value).toBe(ManagerUIState.DISABLED)
|
||||||
})
|
})
|
||||||
@@ -246,115 +271,163 @@ describe('useManagerState', () => {
|
|||||||
|
|
||||||
describe('helper properties', () => {
|
describe('helper properties', () => {
|
||||||
it('isManagerEnabled should return true when state is not DISABLED', () => {
|
it('isManagerEnabled should return true when state is not DISABLED', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref({
|
systemStatsStore.$patch({
|
||||||
system: { argv: ['python', 'main.py', '--enable-manager'] }
|
systemStats: {
|
||||||
}), // Need --enable-manager
|
system: {
|
||||||
isInitialized: ref(true)
|
os: 'Test OS',
|
||||||
} as any)
|
python_version: '3.10',
|
||||||
|
embedded_python: false,
|
||||||
|
comfyui_version: '1.0.0',
|
||||||
|
pytorch_version: '2.0.0',
|
||||||
|
argv: ['python', 'main.py', '--enable-manager'],
|
||||||
|
ram_total: 16000000000,
|
||||||
|
ram_free: 8000000000
|
||||||
|
},
|
||||||
|
devices: []
|
||||||
|
},
|
||||||
|
isInitialized: true
|
||||||
|
})
|
||||||
|
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||||
supports_manager_v4_ui: true
|
supports_manager_v4_ui: true
|
||||||
})
|
})
|
||||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
|
||||||
extensions: []
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
expect(managerState.isManagerEnabled.value).toBe(true)
|
expect(managerState.isManagerEnabled.value).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('isManagerEnabled should return false when state is DISABLED', () => {
|
it('isManagerEnabled should return false when state is DISABLED', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref({
|
systemStatsStore.$patch({
|
||||||
system: { argv: ['python', 'main.py'] } // No --enable-manager flag means disabled
|
systemStats: {
|
||||||
}),
|
system: {
|
||||||
isInitialized: ref(true)
|
os: 'Test OS',
|
||||||
} as any)
|
python_version: '3.10',
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
embedded_python: false,
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
comfyui_version: '1.0.0',
|
||||||
extensions: []
|
pytorch_version: '2.0.0',
|
||||||
} as any)
|
argv: ['python', 'main.py'], // No --enable-manager flag
|
||||||
|
ram_total: 16000000000,
|
||||||
|
ram_free: 8000000000
|
||||||
|
},
|
||||||
|
devices: []
|
||||||
|
},
|
||||||
|
isInitialized: true
|
||||||
|
})
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
expect(managerState.isManagerEnabled.value).toBe(false)
|
expect(managerState.isManagerEnabled.value).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('isNewManagerUI should return true when state is NEW_UI', () => {
|
it('isNewManagerUI should return true when state is NEW_UI', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref({
|
systemStatsStore.$patch({
|
||||||
system: { argv: ['python', 'main.py', '--enable-manager'] }
|
systemStats: {
|
||||||
}), // Need --enable-manager
|
system: {
|
||||||
isInitialized: ref(true)
|
os: 'Test OS',
|
||||||
} as any)
|
python_version: '3.10',
|
||||||
|
embedded_python: false,
|
||||||
|
comfyui_version: '1.0.0',
|
||||||
|
pytorch_version: '2.0.0',
|
||||||
|
argv: ['python', 'main.py', '--enable-manager'],
|
||||||
|
ram_total: 16000000000,
|
||||||
|
ram_free: 8000000000
|
||||||
|
},
|
||||||
|
devices: []
|
||||||
|
},
|
||||||
|
isInitialized: true
|
||||||
|
})
|
||||||
|
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||||
supports_manager_v4_ui: true
|
supports_manager_v4_ui: true
|
||||||
})
|
})
|
||||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
|
||||||
extensions: []
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
expect(managerState.isNewManagerUI.value).toBe(true)
|
expect(managerState.isNewManagerUI.value).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('isLegacyManagerUI should return true when state is LEGACY_UI', () => {
|
it('isLegacyManagerUI should return true when state is LEGACY_UI', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref({
|
systemStatsStore.$patch({
|
||||||
|
systemStats: {
|
||||||
system: {
|
system: {
|
||||||
|
os: 'Test OS',
|
||||||
|
python_version: '3.10',
|
||||||
|
embedded_python: false,
|
||||||
|
comfyui_version: '1.0.0',
|
||||||
|
pytorch_version: '2.0.0',
|
||||||
argv: [
|
argv: [
|
||||||
'python',
|
'python',
|
||||||
'main.py',
|
'main.py',
|
||||||
'--enable-manager',
|
'--enable-manager',
|
||||||
'--enable-manager-legacy-ui'
|
'--enable-manager-legacy-ui'
|
||||||
]
|
],
|
||||||
} // Both flags needed
|
ram_total: 16000000000,
|
||||||
}),
|
ram_free: 8000000000
|
||||||
isInitialized: ref(true)
|
},
|
||||||
} as any)
|
devices: []
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
},
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
isInitialized: true
|
||||||
extensions: []
|
})
|
||||||
} as any)
|
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
expect(managerState.isLegacyManagerUI.value).toBe(true)
|
expect(managerState.isLegacyManagerUI.value).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shouldShowInstallButton should return true only for NEW_UI', () => {
|
it('shouldShowInstallButton should return true only for NEW_UI', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref({
|
systemStatsStore.$patch({
|
||||||
system: { argv: ['python', 'main.py', '--enable-manager'] }
|
systemStats: {
|
||||||
}), // Need --enable-manager
|
system: {
|
||||||
isInitialized: ref(true)
|
os: 'Test OS',
|
||||||
} as any)
|
python_version: '3.10',
|
||||||
|
embedded_python: false,
|
||||||
|
comfyui_version: '1.0.0',
|
||||||
|
pytorch_version: '2.0.0',
|
||||||
|
argv: ['python', 'main.py', '--enable-manager'],
|
||||||
|
ram_total: 16000000000,
|
||||||
|
ram_free: 8000000000
|
||||||
|
},
|
||||||
|
devices: []
|
||||||
|
},
|
||||||
|
isInitialized: true
|
||||||
|
})
|
||||||
|
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||||
supports_manager_v4_ui: true
|
supports_manager_v4_ui: true
|
||||||
})
|
})
|
||||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
|
||||||
extensions: []
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
expect(managerState.shouldShowInstallButton.value).toBe(true)
|
expect(managerState.shouldShowInstallButton.value).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shouldShowManagerButtons should return true when not DISABLED', () => {
|
it('shouldShowManagerButtons should return true when not DISABLED', () => {
|
||||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
// Set up store state
|
||||||
systemStats: ref({
|
systemStatsStore.$patch({
|
||||||
system: { argv: ['python', 'main.py', '--enable-manager'] }
|
systemStats: {
|
||||||
}), // Need --enable-manager
|
system: {
|
||||||
isInitialized: ref(true)
|
os: 'Test OS',
|
||||||
} as any)
|
python_version: '3.10',
|
||||||
|
embedded_python: false,
|
||||||
|
comfyui_version: '1.0.0',
|
||||||
|
pytorch_version: '2.0.0',
|
||||||
|
argv: ['python', 'main.py', '--enable-manager'],
|
||||||
|
ram_total: 16000000000,
|
||||||
|
ram_free: 8000000000
|
||||||
|
},
|
||||||
|
devices: []
|
||||||
|
},
|
||||||
|
isInitialized: true
|
||||||
|
})
|
||||||
|
|
||||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||||
supports_manager_v4_ui: true
|
supports_manager_v4_ui: true
|
||||||
})
|
})
|
||||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||||
vi.mocked(useExtensionStore).mockReturnValue({
|
|
||||||
extensions: []
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const managerState = useManagerState()
|
const managerState = useManagerState()
|
||||||
expect(managerState.shouldShowManagerButtons.value).toBe(true)
|
expect(managerState.shouldShowManagerButtons.value).toBe(true)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ describe('versionUtil', () => {
|
|||||||
it('should return null when current version is null', () => {
|
it('should return null when current version is null', () => {
|
||||||
const result = checkVersionCompatibility(
|
const result = checkVersionCompatibility(
|
||||||
'comfyui_version',
|
'comfyui_version',
|
||||||
null as any,
|
null!,
|
||||||
'>=1.0.0'
|
'>=1.0.0'
|
||||||
)
|
)
|
||||||
expect(result).toBeNull()
|
expect(result).toBeNull()
|
||||||
@@ -51,7 +51,7 @@ describe('versionUtil', () => {
|
|||||||
const result = checkVersionCompatibility(
|
const result = checkVersionCompatibility(
|
||||||
'comfyui_version',
|
'comfyui_version',
|
||||||
'1.0.0',
|
'1.0.0',
|
||||||
null as any
|
null!
|
||||||
)
|
)
|
||||||
expect(result).toBeNull()
|
expect(result).toBeNull()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user