mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-19 06:20:10 +00:00
feat: No Explicit Any (#8601)
## Summary - Add `typescript/no-explicit-any` rule to `.oxlintrc.json` to enforce no explicit `any` types - Fix all 40 instances of explicit `any` throughout the codebase - Improve type safety with proper TypeScript types ## Changes Made ### Configuration - Added `typescript/no-explicit-any` rule to `.oxlintrc.json` ### Type Fixes - Replaced `any` with `unknown` for truly unknown types - Updated generic type parameters to use `unknown` defaults instead of `any` - Fixed method `this` parameters to avoid variance issues - Updated component props to match new generic types - Fixed test mocks to use proper type assertions ### Key Files Modified - `src/types/treeExplorerTypes.ts`: Updated TreeExplorerNode interface generics - `src/platform/settings/types.ts`: Fixed SettingParams generic default - `src/lib/litegraph/src/LGraph.ts`: Fixed ParamsArray type constraint - `src/extensions/core/electronAdapter.ts`: Fixed onChange callbacks - `src/views/GraphView.vue`: Added proper type imports - Multiple test files: Fixed type assertions and mocks ## Test Plan - [x] All lint checks pass (`pnpm lint`) - [x] TypeScript compilation succeeds (`pnpm typecheck`) - [x] Pre-commit hooks pass - [x] No regression in functionality ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8601-feat-add-typescript-no-explicit-any-rule-and-fix-all-instances-2fd6d73d365081fd9beef75d5a6daf5b) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org>
This commit is contained in:
committed by
GitHub
parent
92b7437d86
commit
4fc1d2ef5b
@@ -96,6 +96,7 @@
|
||||
"typescript/restrict-template-expressions": "off",
|
||||
"typescript/unbound-method": "off",
|
||||
"typescript/no-floating-promises": "error",
|
||||
"typescript/no-explicit-any": "error",
|
||||
"vue/no-import-compiler-macros": "error",
|
||||
"vue/no-dupe-keys": "error"
|
||||
},
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
nodeContent: ({ context }) => ({
|
||||
class: 'group/tree-node',
|
||||
onClick: (e: MouseEvent) =>
|
||||
onNodeContentClick(e, context.node as RenderedTreeExplorerNode),
|
||||
onNodeContentClick(e, context.node as RenderedTreeExplorerNode<T>),
|
||||
onContextmenu: (e: MouseEvent) =>
|
||||
handleContextMenu(e, context.node as RenderedTreeExplorerNode)
|
||||
handleContextMenu(e, context.node as RenderedTreeExplorerNode<T>)
|
||||
}),
|
||||
nodeToggleButton: () => ({
|
||||
onClick: (e: MouseEvent) => {
|
||||
@@ -36,15 +36,11 @@
|
||||
</Tree>
|
||||
<ContextMenu ref="menu" :model="menuItems" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
<script setup lang="ts" generic="T">
|
||||
import ContextMenu from 'primevue/contextmenu'
|
||||
import type { MenuItem, MenuItemCommandEvent } from 'primevue/menuitem'
|
||||
import Tree from 'primevue/tree'
|
||||
import { computed, provide, ref } from 'vue'
|
||||
import { computed, provide, ref, shallowRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue'
|
||||
@@ -60,6 +56,10 @@ import type {
|
||||
} from '@/types/treeExplorerTypes'
|
||||
import { combineTrees, findNodeByKey } from '@/utils/treeUtil'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const expandedKeys = defineModel<Record<string, boolean>>('expandedKeys', {
|
||||
required: true
|
||||
})
|
||||
@@ -69,13 +69,13 @@ const selectionKeys = defineModel<Record<string, boolean>>('selectionKeys')
|
||||
const storeSelectionKeys = selectionKeys.value !== undefined
|
||||
|
||||
const props = defineProps<{
|
||||
root: TreeExplorerNode
|
||||
root: TreeExplorerNode<T>
|
||||
class?: string
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'nodeClick', node: RenderedTreeExplorerNode, event: MouseEvent): void
|
||||
(e: 'nodeDelete', node: RenderedTreeExplorerNode): void
|
||||
(e: 'contextMenu', node: RenderedTreeExplorerNode, event: MouseEvent): void
|
||||
(e: 'nodeClick', node: RenderedTreeExplorerNode<T>, event: MouseEvent): void
|
||||
(e: 'nodeDelete', node: RenderedTreeExplorerNode<T>): void
|
||||
(e: 'contextMenu', node: RenderedTreeExplorerNode<T>, event: MouseEvent): void
|
||||
}>()
|
||||
|
||||
const {
|
||||
@@ -83,19 +83,19 @@ const {
|
||||
getAddFolderMenuItem,
|
||||
handleFolderCreation,
|
||||
addFolderCommand
|
||||
} = useTreeFolderOperations(
|
||||
/* expandNode */ (node: TreeExplorerNode) => {
|
||||
} = useTreeFolderOperations<T>(
|
||||
/* expandNode */ (node: TreeExplorerNode<T>) => {
|
||||
expandedKeys.value[node.key] = true
|
||||
}
|
||||
)
|
||||
|
||||
const renderedRoot = computed<RenderedTreeExplorerNode>(() => {
|
||||
const renderedRoot = computed<RenderedTreeExplorerNode<T>>(() => {
|
||||
const renderedRoot = fillNodeInfo(props.root)
|
||||
return newFolderNode.value
|
||||
? combineTrees(renderedRoot, newFolderNode.value)
|
||||
: renderedRoot
|
||||
})
|
||||
const getTreeNodeIcon = (node: TreeExplorerNode) => {
|
||||
const getTreeNodeIcon = (node: TreeExplorerNode<T>) => {
|
||||
if (node.getIcon) {
|
||||
const icon = node.getIcon()
|
||||
if (icon) {
|
||||
@@ -111,7 +111,9 @@ const getTreeNodeIcon = (node: TreeExplorerNode) => {
|
||||
const isExpanded = expandedKeys.value?.[node.key] ?? false
|
||||
return isExpanded ? 'pi pi-folder-open' : 'pi pi-folder'
|
||||
}
|
||||
const fillNodeInfo = (node: TreeExplorerNode): RenderedTreeExplorerNode => {
|
||||
const fillNodeInfo = (
|
||||
node: TreeExplorerNode<T>
|
||||
): RenderedTreeExplorerNode<T> => {
|
||||
const children = node.children?.map(fillNodeInfo) ?? []
|
||||
const totalLeaves = node.leaf
|
||||
? 1
|
||||
@@ -128,7 +130,7 @@ const fillNodeInfo = (node: TreeExplorerNode): RenderedTreeExplorerNode => {
|
||||
}
|
||||
const onNodeContentClick = async (
|
||||
e: MouseEvent,
|
||||
node: RenderedTreeExplorerNode
|
||||
node: RenderedTreeExplorerNode<T>
|
||||
) => {
|
||||
if (!storeSelectionKeys) {
|
||||
selectionKeys.value = {}
|
||||
@@ -139,20 +141,22 @@ const onNodeContentClick = async (
|
||||
emit('nodeClick', node, e)
|
||||
}
|
||||
const menu = ref<InstanceType<typeof ContextMenu> | null>(null)
|
||||
const menuTargetNode = ref<RenderedTreeExplorerNode | null>(null)
|
||||
const menuTargetNode = shallowRef<RenderedTreeExplorerNode<T> | null>(null)
|
||||
const extraMenuItems = computed(() => {
|
||||
return menuTargetNode.value?.contextMenuItems
|
||||
? typeof menuTargetNode.value.contextMenuItems === 'function'
|
||||
? menuTargetNode.value.contextMenuItems(menuTargetNode.value)
|
||||
: menuTargetNode.value.contextMenuItems
|
||||
const node = menuTargetNode.value
|
||||
return node?.contextMenuItems
|
||||
? typeof node.contextMenuItems === 'function'
|
||||
? node.contextMenuItems(node)
|
||||
: node.contextMenuItems
|
||||
: []
|
||||
})
|
||||
const renameEditingNode = ref<RenderedTreeExplorerNode | null>(null)
|
||||
const renameEditingNode = shallowRef<RenderedTreeExplorerNode<T> | null>(null)
|
||||
const errorHandling = useErrorHandling()
|
||||
const handleNodeLabelEdit = async (
|
||||
node: RenderedTreeExplorerNode,
|
||||
n: RenderedTreeExplorerNode,
|
||||
newName: string
|
||||
) => {
|
||||
const node = n as RenderedTreeExplorerNode<T>
|
||||
await errorHandling.wrapWithErrorHandlingAsync(
|
||||
async () => {
|
||||
if (node.key === newFolderNode.value?.key) {
|
||||
@@ -170,35 +174,36 @@ const handleNodeLabelEdit = async (
|
||||
provide(InjectKeyHandleEditLabelFunction, handleNodeLabelEdit)
|
||||
|
||||
const { t } = useI18n()
|
||||
const renameCommand = (node: RenderedTreeExplorerNode) => {
|
||||
const renameCommand = (node: RenderedTreeExplorerNode<T>) => {
|
||||
renameEditingNode.value = node
|
||||
}
|
||||
const deleteCommand = async (node: RenderedTreeExplorerNode) => {
|
||||
const deleteCommand = async (node: RenderedTreeExplorerNode<T>) => {
|
||||
await node.handleDelete?.()
|
||||
emit('nodeDelete', node)
|
||||
}
|
||||
const menuItems = computed<MenuItem[]>(() =>
|
||||
[
|
||||
getAddFolderMenuItem(menuTargetNode.value),
|
||||
const menuItems = computed<MenuItem[]>(() => {
|
||||
const node = menuTargetNode.value
|
||||
return [
|
||||
getAddFolderMenuItem(node),
|
||||
{
|
||||
label: t('g.rename'),
|
||||
icon: 'pi pi-file-edit',
|
||||
command: () => {
|
||||
if (menuTargetNode.value) {
|
||||
renameCommand(menuTargetNode.value)
|
||||
if (node) {
|
||||
renameCommand(node)
|
||||
}
|
||||
},
|
||||
visible: menuTargetNode.value?.handleRename !== undefined
|
||||
visible: node?.handleRename !== undefined
|
||||
},
|
||||
{
|
||||
label: t('g.delete'),
|
||||
icon: 'pi pi-trash',
|
||||
command: async () => {
|
||||
if (menuTargetNode.value) {
|
||||
await deleteCommand(menuTargetNode.value)
|
||||
if (node) {
|
||||
await deleteCommand(node)
|
||||
}
|
||||
},
|
||||
visible: menuTargetNode.value?.handleDelete !== undefined,
|
||||
visible: node?.handleDelete !== undefined,
|
||||
isAsync: true // The delete command can be async
|
||||
},
|
||||
...extraMenuItems.value
|
||||
@@ -210,9 +215,12 @@ const menuItems = computed<MenuItem[]>(() =>
|
||||
})
|
||||
: undefined
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
const handleContextMenu = (e: MouseEvent, node: RenderedTreeExplorerNode) => {
|
||||
const handleContextMenu = (
|
||||
e: MouseEvent,
|
||||
node: RenderedTreeExplorerNode<T>
|
||||
) => {
|
||||
menuTargetNode.value = node
|
||||
emit('contextMenu', node, e)
|
||||
if (menuItems.value.filter((item) => item.visible).length > 0) {
|
||||
@@ -224,15 +232,13 @@ const wrapCommandWithErrorHandler = (
|
||||
command: (event: MenuItemCommandEvent) => void,
|
||||
{ isAsync = false }: { isAsync: boolean }
|
||||
) => {
|
||||
const node = menuTargetNode.value
|
||||
return isAsync
|
||||
? errorHandling.wrapWithErrorHandlingAsync(
|
||||
command as (event: MenuItemCommandEvent) => Promise<void>,
|
||||
menuTargetNode.value?.handleError
|
||||
)
|
||||
: errorHandling.wrapWithErrorHandling(
|
||||
command,
|
||||
menuTargetNode.value?.handleError
|
||||
node?.handleError
|
||||
)
|
||||
: errorHandling.wrapWithErrorHandling(command, node?.handleError)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" generic="T">
|
||||
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview'
|
||||
import Badge from 'primevue/badge'
|
||||
import { computed, inject, ref } from 'vue'
|
||||
@@ -53,17 +53,17 @@ import type {
|
||||
} from '@/types/treeExplorerTypes'
|
||||
|
||||
const props = defineProps<{
|
||||
node: RenderedTreeExplorerNode
|
||||
node: RenderedTreeExplorerNode<T>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: 'itemDropped',
|
||||
node: RenderedTreeExplorerNode,
|
||||
data: RenderedTreeExplorerNode
|
||||
node: RenderedTreeExplorerNode<T>,
|
||||
data: RenderedTreeExplorerNode<T>
|
||||
): void
|
||||
(e: 'dragStart', node: RenderedTreeExplorerNode): void
|
||||
(e: 'dragEnd', node: RenderedTreeExplorerNode): void
|
||||
(e: 'dragStart', node: RenderedTreeExplorerNode<T>): void
|
||||
(e: 'dragEnd', node: RenderedTreeExplorerNode<T>): void
|
||||
}>()
|
||||
|
||||
const nodeBadgeText = computed<string>(() => {
|
||||
@@ -80,7 +80,7 @@ const showNodeBadgeText = computed<boolean>(() => nodeBadgeText.value !== '')
|
||||
const isEditing = computed<boolean>(() => props.node.isEditingLabel ?? false)
|
||||
const handleEditLabel = inject(InjectKeyHandleEditLabelFunction)
|
||||
const handleRename = (newName: string) => {
|
||||
handleEditLabel?.(props.node, newName)
|
||||
handleEditLabel?.(props.node as RenderedTreeExplorerNode, newName)
|
||||
}
|
||||
|
||||
const container = ref<HTMLElement | null>(null)
|
||||
@@ -117,9 +117,13 @@ if (props.node.droppable) {
|
||||
onDrop: async (event) => {
|
||||
const dndData = event.source.data as TreeExplorerDragAndDropData
|
||||
if (dndData.type === 'tree-explorer-node') {
|
||||
await props.node.handleDrop?.(dndData)
|
||||
await props.node.handleDrop?.(dndData as TreeExplorerDragAndDropData<T>)
|
||||
canDrop.value = false
|
||||
emit('itemDropped', props.node, dndData.data)
|
||||
emit(
|
||||
'itemDropped',
|
||||
props.node,
|
||||
dndData.data as RenderedTreeExplorerNode<T>
|
||||
)
|
||||
}
|
||||
},
|
||||
onDragEnter: (event) => {
|
||||
|
||||
@@ -50,7 +50,8 @@
|
||||
<template #before-label="{ node: treeNode }">
|
||||
<span
|
||||
v-if="
|
||||
treeNode.data?.isModified || !treeNode.data?.isPersisted
|
||||
(treeNode.data as ComfyWorkflow)?.isModified ||
|
||||
!(treeNode.data as ComfyWorkflow)?.isPersisted
|
||||
"
|
||||
>*</span
|
||||
>
|
||||
|
||||
@@ -215,7 +215,11 @@ const renderedBookmarkedRoot = computed<TreeExplorerNode<ComfyNodeDefImpl>>(
|
||||
}
|
||||
)
|
||||
|
||||
const treeExplorerRef = ref<InstanceType<typeof TreeExplorer> | null>(null)
|
||||
interface TreeExplorerExposed {
|
||||
addFolderCommand: (targetNodeKey: string) => void
|
||||
}
|
||||
|
||||
const treeExplorerRef = ref<TreeExplorerExposed | null>(null)
|
||||
defineExpose({
|
||||
addNewBookmarkFolder: () => treeExplorerRef.value?.addFolderCommand('root')
|
||||
})
|
||||
|
||||
@@ -63,7 +63,7 @@ onUnmounted(() => {
|
||||
})
|
||||
|
||||
const expandedKeys = inject(InjectKeyExpandedKeys)
|
||||
const handleItemDrop = (node: RenderedTreeExplorerNode) => {
|
||||
const handleItemDrop = (node: RenderedTreeExplorerNode<ComfyNodeDefImpl>) => {
|
||||
if (!expandedKeys) return
|
||||
expandedKeys.value[node.key] = true
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { MenuItem } from 'primevue/menuitem'
|
||||
import { ref } from 'vue'
|
||||
import { shallowRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import type { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes'
|
||||
@@ -8,12 +8,14 @@ import type { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes'
|
||||
* Use this to handle folder operations in a tree.
|
||||
* @param expandNode - The function to expand a node.
|
||||
*/
|
||||
export function useTreeFolderOperations(
|
||||
expandNode: (node: RenderedTreeExplorerNode) => void
|
||||
export function useTreeFolderOperations<T>(
|
||||
expandNode: (node: RenderedTreeExplorerNode<T>) => void
|
||||
) {
|
||||
const { t } = useI18n()
|
||||
const newFolderNode = ref<RenderedTreeExplorerNode | null>(null)
|
||||
const addFolderTargetNode = ref<RenderedTreeExplorerNode | null>(null)
|
||||
const newFolderNode = shallowRef<RenderedTreeExplorerNode<T> | null>(null)
|
||||
const addFolderTargetNode = shallowRef<RenderedTreeExplorerNode<T> | null>(
|
||||
null
|
||||
)
|
||||
|
||||
// Generate a unique temporary key for the new folder
|
||||
const generateTempKey = (parentKey: string) => {
|
||||
@@ -37,7 +39,7 @@ export function useTreeFolderOperations(
|
||||
* The command to add a folder to a node via the context menu
|
||||
* @param targetNode - The node where the folder will be added under
|
||||
*/
|
||||
const addFolderCommand = (targetNode: RenderedTreeExplorerNode) => {
|
||||
const addFolderCommand = (targetNode: RenderedTreeExplorerNode<T>) => {
|
||||
expandNode(targetNode)
|
||||
newFolderNode.value = {
|
||||
key: generateTempKey(targetNode.key),
|
||||
@@ -49,13 +51,13 @@ export function useTreeFolderOperations(
|
||||
totalLeaves: 0,
|
||||
badgeText: '',
|
||||
isEditingLabel: true
|
||||
}
|
||||
} as RenderedTreeExplorerNode<T>
|
||||
addFolderTargetNode.value = targetNode
|
||||
}
|
||||
|
||||
// Generate the "Add Folder" menu item
|
||||
const getAddFolderMenuItem = (
|
||||
targetNode: RenderedTreeExplorerNode | null
|
||||
targetNode: RenderedTreeExplorerNode<T> | null
|
||||
): MenuItem => {
|
||||
return {
|
||||
label: t('g.newFolder'),
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
VramManagement
|
||||
} from '@/types/serverArgs'
|
||||
|
||||
export type ServerConfigValue = string | number | true | null | undefined
|
||||
export type ServerConfigValue = string | number | boolean | null | undefined
|
||||
|
||||
export interface ServerConfig<T> extends FormItem {
|
||||
id: string
|
||||
@@ -19,7 +19,7 @@ export interface ServerConfig<T> extends FormItem {
|
||||
getValue?: (value: T) => Record<string, ServerConfigValue>
|
||||
}
|
||||
|
||||
export const SERVER_CONFIG_ITEMS: ServerConfig<any>[] = [
|
||||
export const SERVER_CONFIG_ITEMS = [
|
||||
// Network settings
|
||||
{
|
||||
id: 'listen',
|
||||
|
||||
@@ -19,7 +19,7 @@ import { electronAPI as getElectronAPI } from '@/utils/envUtil'
|
||||
const toastStore = useToastStore()
|
||||
const { staticUrls, buildDocsUrl } = useExternalLink()
|
||||
|
||||
const onChangeRestartApp = (newValue: string, oldValue: string) => {
|
||||
const onChangeRestartApp = (newValue: unknown, oldValue: unknown) => {
|
||||
// Add a delay to allow changes to take effect before restarting.
|
||||
if (oldValue !== undefined && newValue !== oldValue) {
|
||||
electronAPI.restartApp('Restart ComfyUI to apply changes.', 1500)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
@@ -5,10 +6,17 @@ import { ComfyWidgets } from '../../scripts/widgets'
|
||||
|
||||
// Adds defaults for quickly adding nodes with middle click on the input/output
|
||||
|
||||
interface SlotDefaultsExtension extends ComfyExtension {
|
||||
suggestionsNumber: { value: number } | null
|
||||
slot_types_default_out: Record<string, string[]>
|
||||
slot_types_default_in: Record<string, string[]>
|
||||
setDefaults(maxNum?: number | null): void
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Comfy.SlotDefaults',
|
||||
suggestionsNumber: null,
|
||||
init() {
|
||||
init(this: SlotDefaultsExtension) {
|
||||
LiteGraph.search_filter_enabled = true
|
||||
LiteGraph.middle_click_slot_add_default_node = true
|
||||
this.suggestionsNumber = app.ui.settings.addSetting({
|
||||
@@ -24,13 +32,13 @@ app.registerExtension({
|
||||
},
|
||||
defaultValue: 5,
|
||||
onChange: (newVal) => {
|
||||
this.setDefaults(newVal)
|
||||
this.setDefaults(newVal as number)
|
||||
}
|
||||
})
|
||||
},
|
||||
slot_types_default_out: {},
|
||||
slot_types_default_in: {},
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
async beforeRegisterNodeDef(this: SlotDefaultsExtension, nodeType, nodeData) {
|
||||
var nodeId = nodeData.name
|
||||
const inputs = nodeData['input']?.['required'] //only show required inputs to reduce the mess also not logical to create node with optional inputs
|
||||
for (const inputKey in inputs) {
|
||||
@@ -83,22 +91,23 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
var maxNum = this.suggestionsNumber.value
|
||||
var maxNum = this.suggestionsNumber?.value
|
||||
this.setDefaults(maxNum)
|
||||
},
|
||||
setDefaults(maxNum?: number | null) {
|
||||
setDefaults(this: SlotDefaultsExtension, maxNum?: number | null) {
|
||||
LiteGraph.slot_types_default_out = {}
|
||||
LiteGraph.slot_types_default_in = {}
|
||||
|
||||
const max = maxNum ?? undefined
|
||||
for (const type in this.slot_types_default_out) {
|
||||
LiteGraph.slot_types_default_out[type] = this.slot_types_default_out[
|
||||
type
|
||||
].slice(0, maxNum)
|
||||
].slice(0, max)
|
||||
}
|
||||
for (const type in this.slot_types_default_in) {
|
||||
LiteGraph.slot_types_default_in[type] = this.slot_types_default_in[
|
||||
type
|
||||
].slice(0, maxNum)
|
||||
].slice(0, max)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -90,12 +90,13 @@ export interface LGraphState {
|
||||
lastRerouteId: number
|
||||
}
|
||||
|
||||
type ParamsArray<
|
||||
T extends Record<any, any>,
|
||||
K extends MethodNames<T>
|
||||
> = Parameters<T[K]>[1] extends undefined
|
||||
? Parameters<T[K]> | Parameters<T[K]>[0]
|
||||
: Parameters<T[K]>
|
||||
type ParamsArray<T, K extends MethodNames<T>> = Parameters<
|
||||
Extract<T[K], (...args: never[]) => unknown>
|
||||
>[1] extends undefined
|
||||
?
|
||||
| Parameters<Extract<T[K], (...args: never[]) => unknown>>
|
||||
| Parameters<Extract<T[K], (...args: never[]) => unknown>>[0]
|
||||
: Parameters<Extract<T[K], (...args: never[]) => unknown>>
|
||||
|
||||
/** Configuration used by {@link LGraph} `config`. */
|
||||
export interface LGraphConfig {
|
||||
|
||||
@@ -72,7 +72,7 @@ import FormItem from '@/components/common/FormItem.vue'
|
||||
import PanelTemplate from '@/components/dialog/content/setting/PanelTemplate.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
||||
import type { ServerConfig } from '@/constants/serverConfig'
|
||||
import type { ServerConfig, ServerConfigValue } from '@/constants/serverConfig'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { FormItem as FormItemType } from '@/platform/settings/types'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
@@ -133,7 +133,7 @@ onBeforeUnmount(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const translateItem = (item: ServerConfig<any>): FormItemType => {
|
||||
const translateItem = (item: ServerConfig<ServerConfigValue>): FormItemType => {
|
||||
return {
|
||||
...item,
|
||||
name: t(`serverConfigItems.${item.id}.name`, item.name),
|
||||
|
||||
@@ -165,7 +165,9 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
defaultsByInstallVersion: {
|
||||
'1.25.0': 'legacy'
|
||||
},
|
||||
onChange: async (newValue: string, oldValue?: string) => {
|
||||
onChange: async (val: unknown, old?: unknown) => {
|
||||
const newValue = val as string
|
||||
const oldValue = old as string | undefined
|
||||
if (!oldValue) return
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
@@ -194,7 +196,8 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
{ value: 'select', text: 'Select' }
|
||||
],
|
||||
versionAdded: '1.27.4',
|
||||
onChange: async (newValue: string) => {
|
||||
onChange: async (val: unknown) => {
|
||||
const newValue = val as string
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const navigationMode = settingStore.get('Comfy.Canvas.NavigationMode')
|
||||
@@ -223,7 +226,8 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
{ value: 'zoom', text: 'Zoom in/out' }
|
||||
],
|
||||
versionAdded: '1.27.4',
|
||||
onChange: async (newValue: string) => {
|
||||
onChange: async (val: unknown) => {
|
||||
const newValue = val as string
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const navigationMode = settingStore.get('Comfy.Canvas.NavigationMode')
|
||||
@@ -569,7 +573,8 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
type: 'combo',
|
||||
options: ['Disabled', 'Top'],
|
||||
tooltip: 'Enable the redesigned top menu bar.',
|
||||
migrateDeprecatedValue: (value: string) => {
|
||||
migrateDeprecatedValue: (val: unknown) => {
|
||||
const value = val as string
|
||||
// Floating is now supported by dragging the docked actionbar off.
|
||||
if (value === 'Floating') {
|
||||
return 'Top'
|
||||
@@ -585,7 +590,8 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
type: 'combo',
|
||||
options: ['Sidebar', 'Topbar'],
|
||||
defaultValue: 'Topbar',
|
||||
migrateDeprecatedValue: (value: string) => {
|
||||
migrateDeprecatedValue: (val: unknown) => {
|
||||
const value = val as string
|
||||
if (value === 'Topbar (2nd-row)') {
|
||||
return 'Topbar'
|
||||
}
|
||||
@@ -615,9 +621,8 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
defaultValue: [] as Keybinding[],
|
||||
versionAdded: '1.3.7',
|
||||
versionModified: '1.7.3',
|
||||
migrateDeprecatedValue: (
|
||||
value: (Keybinding & { targetSelector?: string })[]
|
||||
) => {
|
||||
migrateDeprecatedValue: (val: unknown) => {
|
||||
const value = val as (Keybinding & { targetSelector?: string })[]
|
||||
return value.map((keybinding) => {
|
||||
if (keybinding.targetSelector === '#graph-canvas') {
|
||||
keybinding.targetElementId = 'graph-canvas-container'
|
||||
@@ -886,7 +891,8 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
type: 'hidden',
|
||||
defaultValue: 'dark',
|
||||
versionModified: '1.6.7',
|
||||
migrateDeprecatedValue(value: string) {
|
||||
migrateDeprecatedValue(val: unknown) {
|
||||
const value = val as string
|
||||
// Legacy custom palettes were prefixed with 'custom_'
|
||||
return value.startsWith('custom_') ? value.replace('custom_', '') : value
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ describe('useSettingStore', () => {
|
||||
name: 'test.setting',
|
||||
type: 'text',
|
||||
defaultValue: 'default',
|
||||
migrateDeprecatedValue: (value: string) => value.toUpperCase()
|
||||
migrateDeprecatedValue: (val: unknown) => (val as string).toUpperCase()
|
||||
}
|
||||
|
||||
store.settingValues['test.setting'] = 'oldvalue'
|
||||
|
||||
@@ -26,11 +26,11 @@ export interface SettingOption {
|
||||
value?: string | number
|
||||
}
|
||||
|
||||
export interface SettingParams<TValue = any> extends FormItem {
|
||||
export interface SettingParams<TValue = unknown> extends FormItem {
|
||||
id: keyof Settings
|
||||
defaultValue: TValue | (() => TValue)
|
||||
defaultsByInstallVersion?: Record<`${number}.${number}.${number}`, TValue>
|
||||
onChange?: (newValue: TValue, oldValue?: TValue) => void
|
||||
onChange?(newValue: TValue, oldValue?: TValue): void
|
||||
// By default category is id.split('.'). However, changing id to assign
|
||||
// new category has poor backward compatibility. Use this field to overwrite
|
||||
// default category from id.
|
||||
|
||||
@@ -137,32 +137,40 @@ export const useExtensionService = () => {
|
||||
}
|
||||
}
|
||||
|
||||
type FunctionPropertyNames<T> = {
|
||||
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never
|
||||
}[keyof T]
|
||||
type RemoveLastAppParam<T> = T extends (
|
||||
...args: [...infer Rest, ComfyApp]
|
||||
) => infer R
|
||||
? (...args: Rest) => R
|
||||
: T
|
||||
|
||||
type ComfyExtensionParamsWithoutApp<T extends keyof ComfyExtension> =
|
||||
RemoveLastAppParam<ComfyExtension[T]>
|
||||
type KnownExtensionMethods = Exclude<keyof ComfyExtension, number | symbol> &
|
||||
string
|
||||
|
||||
type ComfyExtensionMethod<T extends KnownExtensionMethods> =
|
||||
ComfyExtension[T] extends (...args: unknown[]) => unknown
|
||||
? ComfyExtension[T]
|
||||
: (...args: unknown[]) => unknown
|
||||
|
||||
type ComfyExtensionParamsWithoutApp<T extends KnownExtensionMethods> =
|
||||
RemoveLastAppParam<ComfyExtensionMethod<T>>
|
||||
/**
|
||||
* Invoke an extension callback
|
||||
* @param {keyof ComfyExtension} method The extension callback to execute
|
||||
* @param {unknown[]} args Any arguments to pass to the callback
|
||||
* @returns
|
||||
*/
|
||||
const invokeExtensions = <T extends FunctionPropertyNames<ComfyExtension>>(
|
||||
const invokeExtensions = <T extends KnownExtensionMethods>(
|
||||
method: T,
|
||||
...args: Parameters<ComfyExtensionParamsWithoutApp<T>>
|
||||
) => {
|
||||
const results: ReturnType<ComfyExtension[T]>[] = []
|
||||
const results: ReturnType<ComfyExtensionMethod<T>>[] = []
|
||||
for (const ext of extensionStore.enabledExtensions) {
|
||||
if (method in ext) {
|
||||
try {
|
||||
results.push(ext[method](...args, app))
|
||||
const fn = ext[method]
|
||||
if (typeof fn === 'function') {
|
||||
results.push(fn.call(ext, ...args, app))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error calling extension '${ext.name}' method '${method}'`,
|
||||
@@ -183,9 +191,7 @@ export const useExtensionService = () => {
|
||||
* @param {...unknown} args Any arguments to pass to the callback
|
||||
* @returns
|
||||
*/
|
||||
const invokeExtensionsAsync = async <
|
||||
T extends FunctionPropertyNames<ComfyExtension>
|
||||
>(
|
||||
const invokeExtensionsAsync = async <T extends KnownExtensionMethods>(
|
||||
method: T,
|
||||
...args: Parameters<ComfyExtensionParamsWithoutApp<T>>
|
||||
) => {
|
||||
@@ -193,12 +199,17 @@ export const useExtensionService = () => {
|
||||
extensionStore.enabledExtensions.map(async (ext) => {
|
||||
if (method in ext) {
|
||||
try {
|
||||
const fn = ext[method]
|
||||
if (typeof fn !== 'function') {
|
||||
return
|
||||
}
|
||||
|
||||
// Set current extension name for legacy compatibility tracking
|
||||
if (method === 'setup') {
|
||||
legacyMenuCompat.setCurrentExtension(ext.name)
|
||||
}
|
||||
|
||||
const result = await ext[method](...args, app)
|
||||
const result = await fn.call(ext, ...args, app)
|
||||
|
||||
// Clear current extension after setup
|
||||
if (method === 'setup') {
|
||||
|
||||
@@ -144,7 +144,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
|
||||
if (existingOutput && outputs) {
|
||||
for (const k in outputs) {
|
||||
const existingValue = existingOutput[k]
|
||||
const newValue = (outputs as Record<NodeLocatorId, any>)[k]
|
||||
const newValue = (outputs as Record<NodeLocatorId, unknown>)[k]
|
||||
|
||||
if (Array.isArray(existingValue) && Array.isArray(newValue)) {
|
||||
existingOutput[k] = existingValue.concat(newValue)
|
||||
|
||||
@@ -636,11 +636,11 @@ describe('useModelToNodeStore', () => {
|
||||
const modelToNodeStore = useModelToNodeStore()
|
||||
modelToNodeStore.registerDefaults()
|
||||
|
||||
expect(modelToNodeStore.getNodeProvider(null as any)).toBeUndefined()
|
||||
expect(modelToNodeStore.getNodeProvider(undefined as any)).toBeUndefined()
|
||||
expect(modelToNodeStore.getNodeProvider(123 as any)).toBeUndefined()
|
||||
expect(modelToNodeStore.getAllNodeProviders(null as any)).toEqual([])
|
||||
expect(modelToNodeStore.getAllNodeProviders(undefined as any)).toEqual([])
|
||||
expect(modelToNodeStore.getNodeProvider(null)).toBeUndefined()
|
||||
expect(modelToNodeStore.getNodeProvider(undefined)).toBeUndefined()
|
||||
expect(modelToNodeStore.getNodeProvider(123)).toBeUndefined()
|
||||
expect(modelToNodeStore.getAllNodeProviders(null)).toEqual([])
|
||||
expect(modelToNodeStore.getAllNodeProviders(undefined)).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -104,7 +104,8 @@ export const useModelToNodeStore = defineStore('modelToNode', () => {
|
||||
* @param modelType The name of the model type to get the node provider for.
|
||||
* @returns The node provider for the given model type name.
|
||||
*/
|
||||
function getNodeProvider(modelType: string): ModelNodeProvider | undefined {
|
||||
function getNodeProvider(modelType: unknown): ModelNodeProvider | undefined {
|
||||
if (typeof modelType !== 'string') return undefined
|
||||
registerDefaults()
|
||||
return findProvidersWithFallback(modelType)?.[0]
|
||||
}
|
||||
@@ -115,7 +116,8 @@ export const useModelToNodeStore = defineStore('modelToNode', () => {
|
||||
* @param modelType The name of the model type to get the node providers for.
|
||||
* @returns The list of all valid node providers for the given model type name.
|
||||
*/
|
||||
function getAllNodeProviders(modelType: string): ModelNodeProvider[] {
|
||||
function getAllNodeProviders(modelType: unknown): ModelNodeProvider[] {
|
||||
if (typeof modelType !== 'string') return []
|
||||
registerDefaults()
|
||||
return findProvidersWithFallback(modelType) ?? []
|
||||
}
|
||||
|
||||
@@ -31,6 +31,13 @@ enum TaskItemDisplayStatus {
|
||||
Cancelled = 'Cancelled'
|
||||
}
|
||||
|
||||
interface ResultItemInit extends ResultItem {
|
||||
nodeId: NodeId
|
||||
mediaType: string
|
||||
format?: string
|
||||
frame_rate?: number
|
||||
}
|
||||
|
||||
export class ResultItemImpl {
|
||||
filename: string
|
||||
subfolder: string
|
||||
@@ -44,7 +51,7 @@ export class ResultItemImpl {
|
||||
format?: string
|
||||
frame_rate?: number
|
||||
|
||||
constructor(obj: Record<string, any>) {
|
||||
constructor(obj: ResultItemInit) {
|
||||
this.filename = obj.filename ?? ''
|
||||
this.subfolder = obj.subfolder ?? ''
|
||||
this.type = obj.type ?? ''
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import type { ServerConfig } from '@/constants/serverConfig'
|
||||
import type { ServerConfig, ServerConfigValue } from '@/constants/serverConfig'
|
||||
import type { FormItem } from '@/platform/settings/types'
|
||||
import { useServerConfigStore } from '@/stores/serverConfigStore'
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('useServerConfigStore', () => {
|
||||
})
|
||||
|
||||
it('should load server configs with default values', () => {
|
||||
const configs: ServerConfig<any>[] = [
|
||||
const configs: ServerConfig<ServerConfigValue>[] = [
|
||||
{
|
||||
...dummyFormItem,
|
||||
id: 'test.config1',
|
||||
@@ -50,7 +50,7 @@ describe('useServerConfigStore', () => {
|
||||
})
|
||||
|
||||
it('should load server configs with provided values', () => {
|
||||
const configs: ServerConfig<any>[] = [
|
||||
const configs: ServerConfig<ServerConfigValue>[] = [
|
||||
{
|
||||
...dummyFormItem,
|
||||
id: 'test.config1',
|
||||
@@ -68,7 +68,7 @@ describe('useServerConfigStore', () => {
|
||||
})
|
||||
|
||||
it('should organize configs by category', () => {
|
||||
const configs: ServerConfig<any>[] = [
|
||||
const configs: ServerConfig<ServerConfigValue>[] = [
|
||||
{
|
||||
...dummyFormItem,
|
||||
id: 'test.config1',
|
||||
@@ -97,7 +97,7 @@ describe('useServerConfigStore', () => {
|
||||
})
|
||||
|
||||
it('should generate server config values excluding defaults', () => {
|
||||
const configs: ServerConfig<any>[] = [
|
||||
const configs: ServerConfig<ServerConfigValue>[] = [
|
||||
{
|
||||
...dummyFormItem,
|
||||
id: 'test.config1',
|
||||
@@ -121,12 +121,12 @@ describe('useServerConfigStore', () => {
|
||||
})
|
||||
|
||||
it('should generate launch arguments with custom getValue function', () => {
|
||||
const configs: ServerConfig<any>[] = [
|
||||
const configs: ServerConfig<ServerConfigValue>[] = [
|
||||
{
|
||||
...dummyFormItem,
|
||||
id: 'test.config1',
|
||||
defaultValue: 'default1',
|
||||
getValue: (value: string) => ({ customArg: value })
|
||||
getValue: (value: ServerConfigValue) => ({ customArg: value })
|
||||
},
|
||||
{
|
||||
...dummyFormItem,
|
||||
@@ -146,7 +146,7 @@ describe('useServerConfigStore', () => {
|
||||
})
|
||||
|
||||
it('should not include default values in launch arguments', () => {
|
||||
const configs: ServerConfig<any>[] = [
|
||||
const configs: ServerConfig<ServerConfigValue>[] = [
|
||||
{
|
||||
...dummyFormItem,
|
||||
id: 'test.config1',
|
||||
@@ -170,7 +170,7 @@ describe('useServerConfigStore', () => {
|
||||
})
|
||||
|
||||
it('should not include nullish values in launch arguments', () => {
|
||||
const configs: ServerConfig<any>[] = [
|
||||
const configs: ServerConfig<ServerConfigValue>[] = [
|
||||
{ ...dummyFormItem, id: 'test.config1', defaultValue: 'default1' },
|
||||
{ ...dummyFormItem, id: 'test.config2', defaultValue: 'default2' },
|
||||
{ ...dummyFormItem, id: 'test.config3', defaultValue: 'default3' },
|
||||
|
||||
@@ -259,5 +259,5 @@ export interface ComfyExtension {
|
||||
*/
|
||||
onAuthUserLogout?(): Promise<void> | void
|
||||
|
||||
[key: string]: any
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ export interface TreeNode extends PrimeVueTreeNode {
|
||||
children?: this[]
|
||||
}
|
||||
|
||||
export interface TreeExplorerNode<T = any> extends TreeNode {
|
||||
data?: T
|
||||
export interface TreeExplorerNode<T = unknown> extends TreeNode {
|
||||
readonly data?: T
|
||||
children?: this[]
|
||||
icon?: string
|
||||
/**
|
||||
@@ -46,7 +46,7 @@ export interface TreeExplorerNode<T = any> extends TreeNode {
|
||||
/** Function to handle dropping a node */
|
||||
handleDrop?: (
|
||||
this: TreeExplorerNode<T>,
|
||||
data: TreeExplorerDragAndDropData
|
||||
data: TreeExplorerDragAndDropData<T>
|
||||
) => void | Promise<void>
|
||||
/** Function to handle clicking a node */
|
||||
handleClick?: (
|
||||
@@ -58,10 +58,12 @@ export interface TreeExplorerNode<T = any> extends TreeNode {
|
||||
/** Extra context menu items */
|
||||
contextMenuItems?:
|
||||
| MenuItem[]
|
||||
| ((targetNode: RenderedTreeExplorerNode) => MenuItem[])
|
||||
| ((targetNode: RenderedTreeExplorerNode<T>) => MenuItem[])
|
||||
}
|
||||
|
||||
export interface RenderedTreeExplorerNode<T = any> extends TreeExplorerNode<T> {
|
||||
export interface RenderedTreeExplorerNode<
|
||||
T = unknown
|
||||
> extends TreeExplorerNode<T> {
|
||||
children?: this[]
|
||||
icon: string
|
||||
type: 'folder' | 'node'
|
||||
@@ -73,7 +75,7 @@ export interface RenderedTreeExplorerNode<T = any> extends TreeExplorerNode<T> {
|
||||
isEditingLabel?: boolean
|
||||
}
|
||||
|
||||
export type TreeExplorerDragAndDropData<T = any> = {
|
||||
export type TreeExplorerDragAndDropData<T = unknown> = {
|
||||
type: 'tree-explorer-node'
|
||||
data: RenderedTreeExplorerNode<T>
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ import { useCoreCommands } from '@/composables/useCoreCommands'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import { useProgressFavicon } from '@/composables/useProgressFavicon'
|
||||
import { SERVER_CONFIG_ITEMS } from '@/constants/serverConfig'
|
||||
import type { ServerConfig, ServerConfigValue } from '@/constants/serverConfig'
|
||||
import { i18n, loadLocale } from '@/i18n'
|
||||
import ModelImportProgressDialog from '@/platform/assets/components/ModelImportProgressDialog.vue'
|
||||
import { isCloud, isDesktop } from '@/platform/distribution/types'
|
||||
@@ -344,7 +345,7 @@ const onGraphReady = () => {
|
||||
|
||||
// Load server config
|
||||
wrapWithErrorHandling(useServerConfigStore().loadServerConfig)(
|
||||
SERVER_CONFIG_ITEMS,
|
||||
SERVER_CONFIG_ITEMS as ServerConfig<ServerConfigValue>[],
|
||||
settingStore.get('Comfy.Server.ServerConfigValues')
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,23 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import NodeConflictDialogContent from '@/workbench/extensions/manager/components/manager/NodeConflictDialogContent.vue'
|
||||
import type { ConflictDetectionResult } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
|
||||
import type {
|
||||
ConflictDetail,
|
||||
ConflictDetectionResult
|
||||
} from '@/workbench/extensions/manager/types/conflictDetectionTypes'
|
||||
|
||||
// Type for component VM
|
||||
interface NodeConflictDialogVM {
|
||||
importFailedExpanded: boolean
|
||||
conflictsExpanded: boolean
|
||||
extensionsExpanded: boolean
|
||||
allConflictDetails: ConflictDetail[]
|
||||
importFailedConflicts: string[]
|
||||
}
|
||||
|
||||
function getVM(wrapper: ReturnType<typeof mount>): NodeConflictDialogVM {
|
||||
return wrapper.vm as Partial<NodeConflictDialogVM> as NodeConflictDialogVM
|
||||
}
|
||||
|
||||
// Mock getConflictMessage utility
|
||||
vi.mock('@/utils/conflictMessageUtil', () => ({
|
||||
@@ -288,25 +304,28 @@ describe('NodeConflictDialogContent', () => {
|
||||
await importFailedHeader.trigger('click')
|
||||
|
||||
// Verify import failed panel is open
|
||||
expect((wrapper.vm as any).importFailedExpanded).toBe(true)
|
||||
expect((wrapper.vm as any).conflictsExpanded).toBe(false)
|
||||
expect((wrapper.vm as any).extensionsExpanded).toBe(false)
|
||||
const vm1 = getVM(wrapper)
|
||||
expect(vm1.importFailedExpanded).toBe(true)
|
||||
expect(vm1.conflictsExpanded).toBe(false)
|
||||
expect(vm1.extensionsExpanded).toBe(false)
|
||||
|
||||
// Open conflicts panel
|
||||
await conflictsHeader.trigger('click')
|
||||
|
||||
// Verify conflicts panel is open and others are closed
|
||||
expect((wrapper.vm as any).importFailedExpanded).toBe(false)
|
||||
expect((wrapper.vm as any).conflictsExpanded).toBe(true)
|
||||
expect((wrapper.vm as any).extensionsExpanded).toBe(false)
|
||||
const vm2 = getVM(wrapper)
|
||||
expect(vm2.importFailedExpanded).toBe(false)
|
||||
expect(vm2.conflictsExpanded).toBe(true)
|
||||
expect(vm2.extensionsExpanded).toBe(false)
|
||||
|
||||
// Open extensions panel
|
||||
await extensionsHeader.trigger('click')
|
||||
|
||||
// Verify extensions panel is open and others are closed
|
||||
expect((wrapper.vm as any).importFailedExpanded).toBe(false)
|
||||
expect((wrapper.vm as any).conflictsExpanded).toBe(false)
|
||||
expect((wrapper.vm as any).extensionsExpanded).toBe(true)
|
||||
const vm3 = getVM(wrapper)
|
||||
expect(vm3.importFailedExpanded).toBe(false)
|
||||
expect(vm3.conflictsExpanded).toBe(false)
|
||||
expect(vm3.extensionsExpanded).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -451,10 +470,12 @@ describe('NodeConflictDialogContent', () => {
|
||||
const wrapper = createWrapper()
|
||||
|
||||
// Verify that import_failed conflicts are filtered out from main conflicts
|
||||
const vm = wrapper.vm as any
|
||||
const vm = getVM(wrapper)
|
||||
expect(vm.allConflictDetails).toHaveLength(3) // Should not include import_failed
|
||||
expect(
|
||||
vm.allConflictDetails.every((c: any) => c.type !== 'import_failed')
|
||||
vm.allConflictDetails.every(
|
||||
(c: ConflictDetail) => c.type !== 'import_failed'
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
@@ -463,7 +484,7 @@ describe('NodeConflictDialogContent', () => {
|
||||
const wrapper = createWrapper()
|
||||
|
||||
// Verify that only import_failed packages are extracted
|
||||
const vm = wrapper.vm as any
|
||||
const vm = getVM(wrapper)
|
||||
expect(vm.importFailedConflicts).toHaveLength(1)
|
||||
expect(vm.importFailedConflicts[0]).toBe('Test Package 3')
|
||||
})
|
||||
|
||||
@@ -75,7 +75,7 @@ describe('PackVersionBadge', () => {
|
||||
|
||||
const mountComponent = ({
|
||||
props = {}
|
||||
}: Record<string, any> = {}): VueWrapper => {
|
||||
}: { props?: Record<string, unknown> } = {}): VueWrapper => {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
|
||||
@@ -17,6 +17,14 @@ import enMessages from '@/locales/en/main.json' with { type: 'json' }
|
||||
|
||||
import PackVersionSelectorPopover from './PackVersionSelectorPopover.vue'
|
||||
|
||||
interface PackVersionSelectorVM {
|
||||
getVersionCompatibility: (version: string) => unknown
|
||||
}
|
||||
|
||||
function getVM(wrapper: VueWrapper): PackVersionSelectorVM {
|
||||
return wrapper.vm as Partial<PackVersionSelectorVM> as PackVersionSelectorVM
|
||||
}
|
||||
|
||||
// Default mock versions for reference
|
||||
const defaultMockVersions = [
|
||||
{
|
||||
@@ -106,7 +114,7 @@ describe('PackVersionSelectorPopover', () => {
|
||||
|
||||
const mountComponent = ({
|
||||
props = {}
|
||||
}: Record<string, any> = {}): VueWrapper => {
|
||||
}: { props?: Record<string, unknown> } = {}): VueWrapper => {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
@@ -481,7 +489,7 @@ describe('PackVersionSelectorPopover', () => {
|
||||
mockCheckNodeCompatibility.mockClear()
|
||||
|
||||
// Trigger compatibility check by accessing getVersionCompatibility
|
||||
const vm = wrapper.vm as any
|
||||
const vm = getVM(wrapper)
|
||||
vm.getVersionCompatibility('1.0.0')
|
||||
|
||||
// Verify that checkNodeCompatibility was called with correct data
|
||||
@@ -569,7 +577,7 @@ describe('PackVersionSelectorPopover', () => {
|
||||
})
|
||||
await waitForPromises()
|
||||
|
||||
const vm = wrapper.vm as any
|
||||
const vm = getVM(wrapper)
|
||||
|
||||
// Clear previous calls from component mounting/rendering
|
||||
mockCheckNodeCompatibility.mockClear()
|
||||
|
||||
@@ -17,7 +17,7 @@ vi.mock('es-toolkit/compat', async () => {
|
||||
const actual = await vi.importActual('es-toolkit/compat')
|
||||
return {
|
||||
...actual,
|
||||
debounce: <T extends (...args: any[]) => any>(fn: T) => fn
|
||||
debounce: <T extends (...args: unknown[]) => unknown>(fn: T) => fn
|
||||
}
|
||||
})
|
||||
|
||||
@@ -61,7 +61,10 @@ describe('PackEnableToggle', () => {
|
||||
const mountComponent = ({
|
||||
props = {},
|
||||
installedPacks = {}
|
||||
}: Record<string, any> = {}): VueWrapper => {
|
||||
}: {
|
||||
props?: Record<string, unknown>
|
||||
installedPacks?: Record<string, unknown>
|
||||
} = {}): VueWrapper => {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
@@ -73,7 +76,9 @@ describe('PackEnableToggle', () => {
|
||||
enablePack: mockEnablePack,
|
||||
disablePack: mockDisablePack,
|
||||
installedPacks
|
||||
} as any)
|
||||
} as Partial<ReturnType<typeof useComfyManagerStore>> as ReturnType<
|
||||
typeof useComfyManagerStore
|
||||
>)
|
||||
|
||||
return mount(PackEnableToggle, {
|
||||
props: {
|
||||
|
||||
Reference in New Issue
Block a user