From cabd08f0ec89eaa32b0b389367cee34356aca82c Mon Sep 17 00:00:00 2001 From: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:03:17 +0100 Subject: [PATCH] Road to No explicit any: Group 8 (part 6) test files (#8344) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 all instances of "as unknown as" patterns from test files - Used proper factory functions from litegraphTestUtils instead of custom mocks - Made incomplete mocks explicit using Partial types - Fixed DialogStore mocking with proper interface exports - Improved type safety with satisfies operator where applicable #### App Parameter Removal - **Removed the unused `app` parameter from all ComfyExtension interface methods** - The app parameter was always undefined at runtime as it was never passed from invokeExtensions - Affected methods: init, setup, addCustomNodeDefs, beforeRegisterNodeDef, beforeRegisterVueAppNodeDefs, registerCustomNodes, loadedGraphNode, nodeCreated, beforeConfigureGraph, afterConfigureGraph ##### Breaking Change Analysis Verified via Sourcegraph that this is NOT a breaking change: - Searched all 10 affected methods across GitHub repositories - Only one external repository ([drawthingsai/draw-things-comfyui](https://github.com/drawthingsai/draw-things-comfyui)) declares the app parameter in their extension methods - That repository never actually uses the app parameter (just declares it in the function signature) - All other repositories already omit the app parameter - Search queries used: - [init method search](https://sourcegraph.com/search?q=context:global+repo:%5Egithub%5C.com/.*+lang:typescript+%22init%28app%22+-repo:Comfy-Org/ComfyUI_frontend&patternType=standard) - [setup method search](https://sourcegraph.com/search?q=context:global+repo:%5Egithub%5C.com/.*+lang:typescript+%22setup%28app%22+-repo:Comfy-Org/ComfyUI_frontend&patternType=standard) - Similar searches for all 10 methods confirmed no usage ### Files Changed Test files: - src/components/settings/widgets/__tests__/WidgetInputNumberInput.test.ts - src/services/keybindingService.escape.test.ts - src/services/keybindingService.forwarding.test.ts - src/utils/__tests__/newUserService.test.ts → src/utils/__tests__/useNewUserService.test.ts - src/services/jobOutputCache.test.ts - src/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget.test.ts - src/renderer/extensions/vueNodes/widgets/composables/useIntWidget.test.ts - src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.test.ts Source files: - src/types/comfy.ts - Removed app parameter from ComfyExtension interface - src/services/extensionService.ts - Improved type safety with FunctionPropertyNames helper - src/scripts/metadata/isobmff.ts - Fixed extractJson return type per review - src/extensions/core/*.ts - Updated extension implementations - src/scripts/app.ts - Updated app initialization ### Testing - All existing tests pass - Type checking passes - ESLint/oxlint checks pass - No breaking changes for external repositories Part of the "Road to No Explicit Any" initiative. ### 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 (this PR) --- src/extensions/core/clipspace.ts | 2 +- src/extensions/core/rerouteNode.ts | 2 +- src/extensions/core/saveImageExtraOutput.ts | 2 +- src/extensions/core/widgetInputs.ts | 2 +- .../vueNodes/components/LGraphNode.test.ts | 2 +- .../widgets/components/WidgetGalleria.test.ts | 2 +- .../components/WidgetInputNumberInput.test.ts | 5 +- .../components/form/FormSelectButton.test.ts | 10 +- .../composables/useFloatWidget.test.ts | 8 +- .../widgets/composables/useIntWidget.test.ts | 8 +- .../composables/useRemoteWidget.test.ts | 110 ++++++++++-------- src/scripts/api.featureFlags.test.ts | 15 ++- src/scripts/app.ts | 3 +- src/scripts/metadata/isobmff.ts | 8 +- src/scripts/ui.ts | 2 +- src/services/extensionService.ts | 33 ++++-- .../gateway/registrySearchGateway.test.ts | 4 +- src/services/jobOutputCache.test.ts | 7 +- src/services/keybindingService.escape.test.ts | 16 ++- .../keybindingService.forwarding.test.ts | 36 +++--- src/services/mediaCacheService.test.ts | 2 +- src/stores/dialogStore.ts | 2 +- src/types/comfy.ts | 22 ++-- src/utils/__tests__/litegraphTestUtils.ts | 2 +- 24 files changed, 183 insertions(+), 122 deletions(-) diff --git a/src/extensions/core/clipspace.ts b/src/extensions/core/clipspace.ts index 2fe7378de..0f4ff0d9e 100644 --- a/src/extensions/core/clipspace.ts +++ b/src/extensions/core/clipspace.ts @@ -187,7 +187,7 @@ export class ClipspaceDialog extends ComfyDialog { app.registerExtension({ name: 'Comfy.Clipspace', - init(app) { + init() { app.openClipspace = function () { if (!ClipspaceDialog.instance) { ClipspaceDialog.instance = new ClipspaceDialog() diff --git a/src/extensions/core/rerouteNode.ts b/src/extensions/core/rerouteNode.ts index 44c3f3150..522ce1436 100644 --- a/src/extensions/core/rerouteNode.ts +++ b/src/extensions/core/rerouteNode.ts @@ -13,7 +13,7 @@ import { getWidgetConfig, mergeIfValid, setWidgetConfig } from './widgetInputs' app.registerExtension({ name: 'Comfy.RerouteNode', - registerCustomNodes(app) { + registerCustomNodes() { interface RerouteNode extends LGraphNode { __outputType?: string | number } diff --git a/src/extensions/core/saveImageExtraOutput.ts b/src/extensions/core/saveImageExtraOutput.ts index f216f31a7..d54ac1f5e 100644 --- a/src/extensions/core/saveImageExtraOutput.ts +++ b/src/extensions/core/saveImageExtraOutput.ts @@ -21,7 +21,7 @@ const saveNodeTypes = new Set([ app.registerExtension({ name: 'Comfy.SaveImageExtraOutput', - async beforeRegisterNodeDef(nodeType, nodeData, app) { + async beforeRegisterNodeDef(nodeType, nodeData) { if (saveNodeTypes.has(nodeData.name)) { const onNodeCreated = nodeType.prototype.onNodeCreated // When the SaveImage node is created we want to override the serialization of the output name widget to run our S&R diff --git a/src/extensions/core/widgetInputs.ts b/src/extensions/core/widgetInputs.ts index 879e79976..630f2b092 100644 --- a/src/extensions/core/widgetInputs.ts +++ b/src/extensions/core/widgetInputs.ts @@ -511,7 +511,7 @@ export function mergeIfValid( app.registerExtension({ name: 'Comfy.WidgetInputs', - async beforeRegisterNodeDef(nodeType, _nodeData, app) { + async beforeRegisterNodeDef(nodeType, _nodeData) { // @ts-expect-error adding extra property nodeType.prototype.convertWidgetToInput = function (this: LGraphNode) { console.warn( diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.test.ts b/src/renderer/extensions/vueNodes/components/LGraphNode.test.ts index b93b360fe..5b61a50e7 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.test.ts +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.test.ts @@ -76,7 +76,7 @@ vi.mock( executing: computed(() => mockData.mockExecuting), progress: computed(() => undefined), progressPercentage: computed(() => undefined), - progressState: computed(() => undefined as any), + progressState: computed(() => undefined), executionState: computed(() => 'idle' as const) })) }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.test.ts index 399edb514..8937d8c50 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.test.ts @@ -290,7 +290,7 @@ describe('WidgetGalleria Image Display', () => { await galleria.vm.$emit('update:activeIndex', 2) // Check that the internal activeIndex ref was updated - const vm = wrapper.vm as any + const vm = wrapper.vm as typeof wrapper.vm & { activeIndex: number } expect(vm.activeIndex).toBe(2) }) }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts index 7eb3890bc..1344bb246 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts @@ -197,13 +197,12 @@ describe('WidgetInputNumberInput Large Integer Precision Handling', () => { describe('WidgetInputNumberInput Edge Cases for Precision Handling', () => { it('handles null/undefined model values gracefully', () => { const widget = createMockWidget(0, 'int') - // Mount with undefined as modelValue const wrapper = mount(WidgetInputNumberInput, { global: { plugins: [i18n] }, props: { widget, - modelValue: undefined as any - } + modelValue: undefined + } as { widget: SimplifiedWidget; modelValue: number | undefined } }) expect(wrapper.findAll('button').length).toBe(2) diff --git a/src/renderer/extensions/vueNodes/widgets/components/form/FormSelectButton.test.ts b/src/renderer/extensions/vueNodes/widgets/components/form/FormSelectButton.test.ts index 088a3f92e..f62a40de0 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/form/FormSelectButton.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/form/FormSelectButton.test.ts @@ -8,7 +8,7 @@ describe('FormSelectButton Core Component', () => { // Type-safe helper for mounting component const mountComponent = ( modelValue: string | null | undefined = null, - options: (string | number | Record)[] = [], + options: unknown[] = [], props: Record = {} ) => { return mount(FormSelectButton, { @@ -17,7 +17,11 @@ describe('FormSelectButton Core Component', () => { }, props: { modelValue, - options: options as any, + options: options as ( + | string + | number + | { label: string; value: string | number } + )[], ...props } }) @@ -474,7 +478,7 @@ describe('FormSelectButton Core Component', () => { }) it('handles mixed type options safely', () => { - const mixedOptions: any[] = [ + const mixedOptions: unknown[] = [ 'string', 123, { label: 'Object', value: 'obj' } diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.test.ts b/src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.test.ts index a2274104c..788aa0518 100644 --- a/src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { INumericWidget } from '@/lib/litegraph/src/types/widgets' import { _for_testing } from '@/renderer/extensions/vueNodes/widgets/composables/useFloatWidget' vi.mock('@/scripts/widgets', () => ({ @@ -16,14 +17,17 @@ const { onFloatValueChange } = _for_testing describe('useFloatWidget', () => { describe('onFloatValueChange', () => { - let widget: any + let widget: INumericWidget beforeEach(() => { // Reset the widget before each test widget = { + type: 'number', + name: 'test_widget', + y: 0, options: {}, value: 0 - } + } as Partial as INumericWidget }) it('should not round values when round option is not set', () => { diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useIntWidget.test.ts b/src/renderer/extensions/vueNodes/widgets/composables/useIntWidget.test.ts index 8f1284026..8a32d55fa 100644 --- a/src/renderer/extensions/vueNodes/widgets/composables/useIntWidget.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/composables/useIntWidget.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { INumericWidget } from '@/lib/litegraph/src/types/widgets' import { _for_testing } from '@/renderer/extensions/vueNodes/widgets/composables/useIntWidget' vi.mock('@/scripts/widgets', () => ({ @@ -16,14 +17,17 @@ const { onValueChange } = _for_testing describe('useIntWidget', () => { describe('onValueChange', () => { - let widget: any + let widget: INumericWidget beforeEach(() => { // Reset the widget before each test widget = { + type: 'number', + name: 'test_widget', + y: 0, options: {}, value: 0 - } + } as Partial as INumericWidget }) it('should round values based on step size', () => { diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget.test.ts b/src/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget.test.ts index 599a3bfab..9e86ccda0 100644 --- a/src/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget.test.ts @@ -3,19 +3,20 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { IWidget } from '@/lib/litegraph/src/litegraph' import { api } from '@/scripts/api' -import { LGraphNode } from '@/lib/litegraph/src/litegraph' import { useRemoteWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget' import type { RemoteWidgetConfig } from '@/schemas/nodeDefSchema' +import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils' -const createMockNode = (overrides: Partial = {}): LGraphNode => { - const node = new LGraphNode('TestNode') - Object.assign(node, overrides) - return node +function createMockWidget(overrides: Partial = {}): IWidget { + return { + name: 'test_widget', + type: 'text', + value: '', + options: {}, + ...overrides + } as Partial as IWidget } -const createMockWidget = (overrides = {}): IWidget => - ({ ...overrides }) as unknown as IWidget - const mockCloudAuth = vi.hoisted(() => ({ isCloud: false, authHeader: null as { Authorization: string } | null @@ -67,7 +68,10 @@ function createMockConfig(overrides = {}): RemoteWidgetConfig { const createMockOptions = (inputOverrides = {}) => ({ remoteConfig: createMockConfig(inputOverrides), defaultValue: DEFAULT_VALUE, - node: createMockNode(), + node: createMockLGraphNode({ + addWidget: vi.fn(() => createMockWidget()), + onRemoved: undefined + }), widget: createMockWidget() }) @@ -499,12 +503,14 @@ describe('useRemoteWidget', () => { }) it('should handle rapid cache clearing during fetch', async () => { - let resolvePromise: (value: any) => void - const delayedPromise = new Promise((resolve) => { - resolvePromise = resolve - }) + let resolvePromise: (value: { data: unknown; status?: number }) => void + const delayedPromise = new Promise<{ data: unknown; status?: number }>( + (resolve) => { + resolvePromise = resolve + } + ) - vi.mocked(axios.get).mockImplementationOnce(() => delayedPromise as any) + vi.mocked(axios.get).mockImplementationOnce(() => delayedPromise) const hook = useRemoteWidget(createMockOptions()) hook.getValue() @@ -520,17 +526,20 @@ describe('useRemoteWidget', () => { }) it('should handle widget destroyed during fetch', async () => { - let resolvePromise: (value: any) => void - const delayedPromise = new Promise((resolve) => { - resolvePromise = resolve - }) + let resolvePromise: (value: { data: unknown; status?: number }) => void + const delayedPromise = new Promise<{ data: unknown; status?: number }>( + (resolve) => { + resolvePromise = resolve + } + ) - vi.mocked(axios.get).mockImplementationOnce(() => delayedPromise as any) + vi.mocked(axios.get).mockImplementationOnce(() => delayedPromise) - let hook = useRemoteWidget(createMockOptions()) + let hook: ReturnType | null = + useRemoteWidget(createMockOptions()) const fetchPromise = hook.getValue() - hook = null as any + hook = null resolvePromise!({ data: ['delayed data'] }) await fetchPromise @@ -583,19 +592,19 @@ describe('useRemoteWidget', () => { describe('auto-refresh on task completion', () => { it('should add auto-refresh toggle widget', () => { - const mockNode = { + const mockNode = createMockLGraphNode({ addWidget: vi.fn(), widgets: [] - } - const mockWidget = { + }) + const mockWidget = createMockWidget({ refresh: vi.fn() - } + }) useRemoteWidget({ remoteConfig: createMockConfig(), defaultValue: DEFAULT_VALUE, - node: mockNode as any, - widget: mockWidget as any + node: mockNode, + widget: mockWidget }) // Should add auto-refresh toggle widget @@ -613,19 +622,19 @@ describe('useRemoteWidget', () => { it('should register event listener when enabled', async () => { const addEventListenerSpy = vi.spyOn(api, 'addEventListener') - const mockNode = { + const mockNode = createMockLGraphNode({ addWidget: vi.fn(), widgets: [] - } - const mockWidget = { + }) + const mockWidget = createMockWidget({ refresh: vi.fn() - } + }) useRemoteWidget({ remoteConfig: createMockConfig(), defaultValue: DEFAULT_VALUE, - node: mockNode as any, - widget: mockWidget as any + node: mockNode, + widget: mockWidget }) // Event listener should be registered immediately @@ -644,16 +653,16 @@ describe('useRemoteWidget', () => { } }) - const mockNode = { + const mockNode = createMockLGraphNode({ addWidget: vi.fn(), widgets: [] - } - const mockWidget = {} as any + }) + const mockWidget = createMockWidget({}) useRemoteWidget({ remoteConfig: createMockConfig(), defaultValue: DEFAULT_VALUE, - node: mockNode as any, + node: mockNode, widget: mockWidget }) @@ -661,8 +670,9 @@ describe('useRemoteWidget', () => { const refreshSpy = vi.spyOn(mockWidget, 'refresh') // Get the toggle callback and enable auto-refresh - const toggleCallback = mockNode.addWidget.mock.calls.find( - (call) => call[0] === 'toggle' + const addWidgetMock = mockNode.addWidget as ReturnType + const toggleCallback = addWidgetMock.mock.calls.find( + (call: unknown[]) => call[0] === 'toggle' )?.[3] toggleCallback?.(true) @@ -681,16 +691,16 @@ describe('useRemoteWidget', () => { } }) - const mockNode = { + const mockNode = createMockLGraphNode({ addWidget: vi.fn(), widgets: [] - } - const mockWidget = {} as any + }) + const mockWidget = createMockWidget({}) useRemoteWidget({ remoteConfig: createMockConfig(), defaultValue: DEFAULT_VALUE, - node: mockNode as any, + node: mockNode, widget: mockWidget }) @@ -715,20 +725,20 @@ describe('useRemoteWidget', () => { const removeEventListenerSpy = vi.spyOn(api, 'removeEventListener') - const mockNode = { + const mockNode = createMockLGraphNode({ addWidget: vi.fn(), widgets: [], - onRemoved: undefined as any - } - const mockWidget = { + onRemoved: undefined + }) + const mockWidget = createMockWidget({ refresh: vi.fn() - } + }) useRemoteWidget({ remoteConfig: createMockConfig(), defaultValue: DEFAULT_VALUE, - node: mockNode as any, - widget: mockWidget as any + node: mockNode, + widget: mockWidget }) // Simulate node removal diff --git a/src/scripts/api.featureFlags.test.ts b/src/scripts/api.featureFlags.test.ts index 9900a2ade..970521c7e 100644 --- a/src/scripts/api.featureFlags.test.ts +++ b/src/scripts/api.featureFlags.test.ts @@ -1,10 +1,19 @@ +import type { Mock } from 'vitest' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { api } from '@/scripts/api' +interface MockWebSocket { + readyState: number + send: Mock + close: Mock + addEventListener: Mock + removeEventListener: Mock +} + describe('API Feature Flags', () => { - let mockWebSocket: any - const wsEventHandlers: { [key: string]: (event: any) => void } = {} + let mockWebSocket: MockWebSocket + const wsEventHandlers: { [key: string]: (event: unknown) => void } = {} beforeEach(() => { // Use fake timers @@ -16,7 +25,7 @@ describe('API Feature Flags', () => { send: vi.fn(), close: vi.fn(), addEventListener: vi.fn( - (event: string, handler: (event: any) => void) => { + (event: string, handler: (event: unknown) => void) => { wsEventHandlers[event] = handler } ), diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 796965b27..0c985860c 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -919,8 +919,7 @@ export class ComfyApp { const nodeDefArray: ComfyNodeDefV1[] = Object.values(allNodeDefs) useExtensionService().invokeExtensions( 'beforeRegisterVueAppNodeDefs', - nodeDefArray, - this + nodeDefArray ) nodeDefStore.updateNodeDefs(nodeDefArray) } diff --git a/src/scripts/metadata/isobmff.ts b/src/scripts/metadata/isobmff.ts index a9a7f8826..e50a10169 100644 --- a/src/scripts/metadata/isobmff.ts +++ b/src/scripts/metadata/isobmff.ts @@ -72,7 +72,11 @@ const findIsobmffBoxByType = ( return null } -const extractJson = (data: Uint8Array, start: number, end: number): any => { +const extractJson = ( + data: Uint8Array, + start: number, + end: number +): ComfyWorkflowJSON | ComfyApiWorkflow | null => { let jsonStart = start while (jsonStart < end && data[jsonStart] !== ASCII.OPEN_BRACE) { jsonStart++ @@ -133,7 +137,7 @@ const extractMetadataValueFromDataBox = ( lowerKeyName === ComfyMetadataTags.PROMPT.toLowerCase() || lowerKeyName === ComfyMetadataTags.WORKFLOW.toLowerCase() ) { - return extractJson(data, valueStart, dataBoxEnd) || null + return extractJson(data, valueStart, dataBoxEnd) } return null } diff --git a/src/scripts/ui.ts b/src/scripts/ui.ts index 9a0f9d5cc..0d1b383fb 100644 --- a/src/scripts/ui.ts +++ b/src/scripts/ui.ts @@ -28,7 +28,7 @@ type Props = { style?: Partial for?: string textContent?: string - [key: string]: any + [key: string]: unknown } type Children = Element[] | Element | string | string[] diff --git a/src/services/extensionService.ts b/src/services/extensionService.ts index 75b159e11..b926ca2a6 100644 --- a/src/services/extensionService.ts +++ b/src/services/extensionService.ts @@ -3,7 +3,6 @@ import { useErrorHandling } from '@/composables/useErrorHandling' import { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat' import { useSettingStore } from '@/platform/settings/settingStore' import { api } from '@/scripts/api' -import { app } from '@/scripts/app' import { useCommandStore } from '@/stores/commandStore' import { useExtensionStore } from '@/stores/extensionStore' import { KeybindingImpl, useKeybindingStore } from '@/stores/keybindingStore' @@ -12,6 +11,8 @@ import { useWidgetStore } from '@/stores/widgetStore' import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore' import type { ComfyExtension } from '@/types/comfy' import type { AuthUserInfo } from '@/types/authTypes' +import { app } from '@/scripts/app' +import type { ComfyApp } from '@/scripts/app' export const useExtensionService = () => { const extensionStore = useExtensionStore() @@ -135,14 +136,28 @@ export const useExtensionService = () => { } } + type FunctionPropertyNames = { + [K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never + }[keyof T] + type RemoveLastAppParam = T extends ( + ...args: [...infer Rest, ComfyApp] + ) => infer R + ? (...args: Rest) => R + : T + + type ComfyExtensionParamsWithoutApp = + RemoveLastAppParam /** * Invoke an extension callback * @param {keyof ComfyExtension} method The extension callback to execute - * @param {any[]} args Any arguments to pass to the callback + * @param {unknown[]} args Any arguments to pass to the callback * @returns */ - const invokeExtensions = (method: keyof ComfyExtension, ...args: any[]) => { - const results: any[] = [] + const invokeExtensions = >( + method: T, + ...args: Parameters> + ) => { + const results: ReturnType[] = [] for (const ext of extensionStore.enabledExtensions) { if (method in ext) { try { @@ -164,12 +179,14 @@ export const useExtensionService = () => { * Invoke an async extension callback * Each callback will be invoked concurrently * @param {string} method The extension callback to execute - * @param {...any} args Any arguments to pass to the callback + * @param {...unknown} args Any arguments to pass to the callback * @returns */ - const invokeExtensionsAsync = async ( - method: keyof ComfyExtension, - ...args: any[] + const invokeExtensionsAsync = async < + T extends FunctionPropertyNames + >( + method: T, + ...args: Parameters> ) => { return await Promise.all( extensionStore.enabledExtensions.map(async (ext) => { diff --git a/src/services/gateway/registrySearchGateway.test.ts b/src/services/gateway/registrySearchGateway.test.ts index e51b164d1..511a5a978 100644 --- a/src/services/gateway/registrySearchGateway.test.ts +++ b/src/services/gateway/registrySearchGateway.test.ts @@ -9,8 +9,8 @@ vi.mock('@/services/providers/algoliaSearchProvider') vi.mock('@/services/providers/registrySearchProvider') describe('useRegistrySearchGateway', () => { - let consoleWarnSpy: any - let consoleInfoSpy: any + let consoleWarnSpy: ReturnType + let consoleInfoSpy: ReturnType beforeEach(() => { vi.clearAllMocks() diff --git a/src/services/jobOutputCache.test.ts b/src/services/jobOutputCache.test.ts index 99fbd22f1..615110125 100644 --- a/src/services/jobOutputCache.test.ts +++ b/src/services/jobOutputCache.test.ts @@ -6,6 +6,7 @@ import type { JobDetail, JobListItem } from '@/platform/remote/comfyui/jobs/jobTypes' +import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' import { findActiveIndex, getJobDetail, @@ -257,10 +258,12 @@ describe('jobOutputCache', () => { priority: 0, outputs: {} } - const mockWorkflow = { version: 1 } + const mockWorkflow = { version: 1 } as Partial vi.mocked(api.getJobDetail).mockResolvedValue(mockDetail) - vi.mocked(extractWorkflow).mockResolvedValue(mockWorkflow as any) + vi.mocked(extractWorkflow).mockResolvedValue( + mockWorkflow as ComfyWorkflowJSON + ) const result = await getJobWorkflow(jobId) diff --git a/src/services/keybindingService.escape.test.ts b/src/services/keybindingService.escape.test.ts index caaf10e46..cc1059abc 100644 --- a/src/services/keybindingService.escape.test.ts +++ b/src/services/keybindingService.escape.test.ts @@ -5,6 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { CORE_KEYBINDINGS } from '@/constants/coreKeybindings' import { useKeybindingService } from '@/services/keybindingService' import { useCommandStore } from '@/stores/commandStore' +import type { DialogInstance } from '@/stores/dialogStore' import { useDialogStore } from '@/stores/dialogStore' import { KeyComboImpl, @@ -38,10 +39,13 @@ describe('keybindingService - Escape key handling', () => { mockCommandExecute = vi.fn() commandStore.execute = mockCommandExecute - // Reset dialog store mock to empty + // Reset dialog store mock to empty - only mock the properties we need for testing vi.mocked(useDialogStore).mockReturnValue({ - dialogStack: [] - } as any) + dialogStack: [], + // Add other required properties as undefined/default values to satisfy the type + // but they won't be used in these tests + ...({} as Omit, 'dialogStack'>) + }) keybindingService = useKeybindingService() keybindingService.registerCoreKeybindings() @@ -179,8 +183,10 @@ describe('keybindingService - Escape key handling', () => { it('should not execute Escape keybinding when dialogs are open', async () => { // Mock dialog store to have open dialogs vi.mocked(useDialogStore).mockReturnValue({ - dialogStack: [{ key: 'test-dialog' }] - } as any) + dialogStack: [{ key: 'test-dialog' } as DialogInstance], + // Add other required properties as undefined/default values to satisfy the type + ...({} as Omit, 'dialogStack'>) + }) // Re-create keybinding service to pick up new mock keybindingService = useKeybindingService() diff --git a/src/services/keybindingService.forwarding.test.ts b/src/services/keybindingService.forwarding.test.ts index bd3951f56..eb6d614fd 100644 --- a/src/services/keybindingService.forwarding.test.ts +++ b/src/services/keybindingService.forwarding.test.ts @@ -78,7 +78,9 @@ describe('keybindingService - Event Forwarding', () => { // Reset dialog store mock to empty vi.mocked(useDialogStore).mockReturnValue({ dialogStack: [] - } as any) + } as Partial> as ReturnType< + typeof useDialogStore + >) keybindingService = useKeybindingService() keybindingService.registerCoreKeybindings() @@ -126,33 +128,35 @@ describe('keybindingService - Event Forwarding', () => { }) it('should not forward Delete key when canvas processKey is not available', async () => { - // Temporarily replace processKey with undefined + // Temporarily replace processKey with undefined - testing edge case const originalProcessKey = vi.mocked(app.canvas).processKey - vi.mocked(app.canvas).processKey = undefined as any + vi.mocked(app.canvas).processKey = undefined! const event = createTestKeyboardEvent('Delete') - await keybindingService.keybindHandler(event) - - expect(vi.mocked(useCommandStore().execute)).not.toHaveBeenCalled() - - // Restore processKey for other tests - vi.mocked(app.canvas).processKey = originalProcessKey + try { + await keybindingService.keybindHandler(event) + expect(vi.mocked(useCommandStore().execute)).not.toHaveBeenCalled() + } finally { + // Restore processKey for other tests + vi.mocked(app.canvas).processKey = originalProcessKey + } }) it('should not forward Delete key when canvas is not available', async () => { // Temporarily set canvas to null const originalCanvas = vi.mocked(app).canvas - vi.mocked(app).canvas = null as any + vi.mocked(app).canvas = null! const event = createTestKeyboardEvent('Delete') - await keybindingService.keybindHandler(event) - - expect(vi.mocked(useCommandStore().execute)).not.toHaveBeenCalled() - - // Restore canvas for other tests - vi.mocked(app).canvas = originalCanvas + try { + await keybindingService.keybindHandler(event) + expect(vi.mocked(useCommandStore().execute)).not.toHaveBeenCalled() + } finally { + // Restore canvas for other tests + vi.mocked(app).canvas = originalCanvas + } }) it('should not forward non-canvas keys', async () => { diff --git a/src/services/mediaCacheService.test.ts b/src/services/mediaCacheService.test.ts index 1860344cc..1ecc0658f 100644 --- a/src/services/mediaCacheService.test.ts +++ b/src/services/mediaCacheService.test.ts @@ -7,7 +7,7 @@ global.fetch = vi.fn() global.URL = { createObjectURL: vi.fn(() => 'blob:mock-url'), revokeObjectURL: vi.fn() -} as any +} as Partial as typeof URL describe('mediaCacheService', () => { describe('URL reference counting', () => { diff --git a/src/stores/dialogStore.ts b/src/stores/dialogStore.ts index 333af1a0b..6742b5459 100644 --- a/src/stores/dialogStore.ts +++ b/src/stores/dialogStore.ts @@ -37,7 +37,7 @@ interface CustomDialogComponentProps { export type DialogComponentProps = ComponentAttrs & CustomDialogComponentProps -interface DialogInstance< +export interface DialogInstance< H extends Component = Component, B extends Component = Component, F extends Component = Component diff --git a/src/types/comfy.ts b/src/types/comfy.ts index 436e504ef..57ba2e971 100644 --- a/src/types/comfy.ts +++ b/src/types/comfy.ts @@ -134,18 +134,15 @@ export interface ComfyExtension { actionBarButtons?: ActionBarButton[] /** * Allows any initialisation, e.g. loading resources. Called after the canvas is created but before nodes are added - * @param app The ComfyUI app instance */ init?(app: ComfyApp): Promise | void /** * Allows any additional setup, called after the application is fully set up and running - * @param app The ComfyUI app instance */ setup?(app: ComfyApp): Promise | void /** * Called before nodes are registered with the graph * @param defs The collection of node definitions, add custom ones or edit existing ones - * @param app The ComfyUI app instance */ addCustomNodeDefs?( defs: Record, @@ -155,7 +152,6 @@ export interface ComfyExtension { // getCustomWidgets. /** * Allows the extension to add custom widgets - * @param app The ComfyUI app instance * @returns An array of {[widget name]: widget data} */ getCustomWidgets?(app: ComfyApp): Promise | Widgets @@ -185,7 +181,7 @@ export interface ComfyExtension { * Allows the extension to add additional handling to the node before it is registered with **LGraph** * @param nodeType The node class (not an instance) * @param nodeData The original node object info config object - * @param app The ComfyUI app instance + * @param app The app instance */ beforeRegisterNodeDef?( nodeType: typeof LGraphNode, @@ -198,15 +194,13 @@ export interface ComfyExtension { * Modifications is expected to be made in place. * * @param defs The node definitions - * @param app The ComfyUI app instance + * @param app The app instance */ beforeRegisterVueAppNodeDefs?(defs: ComfyNodeDef[], app: ComfyApp): void /** * Allows the extension to register additional nodes with LGraph after standard nodes are added. * Custom node classes should extend **LGraphNode**. - * - * @param app The ComfyUI app instance */ registerCustomNodes?(app: ComfyApp): Promise | void /** @@ -214,13 +208,13 @@ export interface ComfyExtension { * If you break something in the backend and want to patch workflows in the frontend * This is the place to do this * @param node The node that has been loaded - * @param app The ComfyUI app instance + * @param app The app instance */ loadedGraphNode?(node: LGraphNode, app: ComfyApp): void /** * Allows the extension to run code after the constructor of the node * @param node The node that has been created - * @param app The ComfyUI app instance + * @param app The app instance */ nodeCreated?(node: LGraphNode, app: ComfyApp): void @@ -228,18 +222,22 @@ export interface ComfyExtension { * Allows the extension to modify the graph data before it is configured. * @param graphData The graph data * @param missingNodeTypes The missing node types + * @param app The app instance */ beforeConfigureGraph?( graphData: ComfyWorkflowJSON, - missingNodeTypes: MissingNodeType[] + missingNodeTypes: MissingNodeType[], + app: ComfyApp ): Promise | void /** * Allows the extension to run code after the graph is configured. * @param missingNodeTypes The missing node types + * @param app The app instance */ afterConfigureGraph?( - missingNodeTypes: MissingNodeType[] + missingNodeTypes: MissingNodeType[], + app: ComfyApp ): Promise | void /** diff --git a/src/utils/__tests__/litegraphTestUtils.ts b/src/utils/__tests__/litegraphTestUtils.ts index 05e88e738..c5d241b5c 100644 --- a/src/utils/__tests__/litegraphTestUtils.ts +++ b/src/utils/__tests__/litegraphTestUtils.ts @@ -211,7 +211,7 @@ export function createMockFileList(files: File[]): FileList { * The ChangeTracker requires a proper ComfyWorkflowJSON structure */ export function createMockChangeTracker( - overrides: Record = {} + overrides: Partial = {} ): ChangeTracker { const partial = { activeState: {