mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-27 02:04:09 +00:00
refactor: improve type safety in group 4 files
Replace 'as unknown as' with safer type patterns in 12 files: - Use 'as Partial<Type> as Type' for mock objects - Remove unnecessary double casts where direct typing works Files improved: - LiteGraph interfaces, subgraph tests, and utilities - Platform settings, telemetry, and asset services Part of group 4 cleanup from fix/remove-any-types-part8 branch.
This commit is contained in:
@@ -254,7 +254,10 @@ type KeysOfType<T, Match> = Exclude<
|
||||
>
|
||||
|
||||
/** The names of all (optional) methods and functions in T */
|
||||
export type MethodNames<T> = KeysOfType<T, ((...args: any) => any) | undefined>
|
||||
export type MethodNames<T> = KeysOfType<
|
||||
T,
|
||||
((...args: unknown[]) => unknown) | undefined
|
||||
>
|
||||
export interface NewNodePosition {
|
||||
node: LGraphNode
|
||||
newPos: {
|
||||
@@ -459,28 +462,6 @@ export interface ISubgraphInput extends INodeInputSlot {
|
||||
_subgraphSlot: SubgraphInput
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for {@link Parameters} of optional callbacks.
|
||||
* @example
|
||||
* ```ts
|
||||
* const { onClick } = CustomClass.prototype
|
||||
* CustomClass.prototype.onClick = function (...args: CallbackParams<typeof onClick>) {
|
||||
* const r = onClick?.apply(this, args)
|
||||
* // ...
|
||||
* return r
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export type CallbackParams<T extends ((...args: any) => any) | undefined> =
|
||||
Parameters<Exclude<T, undefined>>
|
||||
|
||||
/**
|
||||
* Shorthand for {@link ReturnType} of optional callbacks.
|
||||
* @see {@link CallbackParams}
|
||||
*/
|
||||
export type CallbackReturn<T extends ((...args: any) => any) | undefined> =
|
||||
ReturnType<Exclude<T, undefined>>
|
||||
|
||||
/**
|
||||
* An object that can be hovered over.
|
||||
*/
|
||||
|
||||
@@ -4,11 +4,7 @@ import { InvalidLinkError } from '@/lib/litegraph/src/infrastructure/InvalidLink
|
||||
import { NullGraphError } from '@/lib/litegraph/src/infrastructure/NullGraphError'
|
||||
import { RecursionError } from '@/lib/litegraph/src/infrastructure/RecursionError'
|
||||
import { SlotIndexError } from '@/lib/litegraph/src/infrastructure/SlotIndexError'
|
||||
import type {
|
||||
CallbackParams,
|
||||
CallbackReturn,
|
||||
ISlotType
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import type { ISlotType } from '@/lib/litegraph/src/interfaces'
|
||||
import { LGraphEventMode, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import type { Subgraph } from './Subgraph'
|
||||
@@ -45,8 +41,8 @@ type ResolvedInput = {
|
||||
*/
|
||||
export class ExecutableNodeDTO implements ExecutableLGraphNode {
|
||||
applyToGraph?(
|
||||
...args: CallbackParams<typeof this.node.applyToGraph>
|
||||
): CallbackReturn<typeof this.node.applyToGraph>
|
||||
...args: Parameters<NonNullable<typeof this.node.applyToGraph>>
|
||||
): ReturnType<NonNullable<typeof this.node.applyToGraph>>
|
||||
|
||||
/** The graph that this node is a part of. */
|
||||
readonly graph: LGraph | Subgraph
|
||||
|
||||
@@ -10,6 +10,12 @@ import {
|
||||
createTestSubgraphNode
|
||||
} from './__fixtures__/subgraphHelpers'
|
||||
|
||||
type InputWithWidget = {
|
||||
_widget?: IWidget | { type: string; value: unknown; name: string }
|
||||
_connection?: { id: number; type: string }
|
||||
_listenerController?: AbortController
|
||||
}
|
||||
|
||||
describe.skip('SubgraphNode Memory Management', () => {
|
||||
describe.skip('Event Listener Cleanup', () => {
|
||||
it('should register event listeners on construction', () => {
|
||||
@@ -308,14 +314,14 @@ describe.skip('SubgraphMemory - Widget Reference Management', () => {
|
||||
|
||||
// Set widget reference
|
||||
if (input && '_widget' in input) {
|
||||
;(input as any)._widget = mockWidget
|
||||
expect((input as any)._widget).toBe(mockWidget)
|
||||
;(input as InputWithWidget)._widget = mockWidget
|
||||
expect((input as InputWithWidget)._widget).toBe(mockWidget)
|
||||
}
|
||||
|
||||
// Clear widget reference
|
||||
if (input && '_widget' in input) {
|
||||
;(input as any)._widget = undefined
|
||||
expect((input as any)._widget).toBeUndefined()
|
||||
;(input as InputWithWidget)._widget = undefined
|
||||
expect((input as InputWithWidget)._widget).toBeUndefined()
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -360,30 +366,34 @@ describe.skip('SubgraphMemory - Widget Reference Management', () => {
|
||||
|
||||
// Set up references that should be cleaned up
|
||||
const mockReferences = {
|
||||
widget: { type: 'number', value: 42 },
|
||||
widget: { type: 'number', value: 42, name: 'mock_widget' },
|
||||
connection: { id: 1, type: 'number' },
|
||||
listener: vi.fn()
|
||||
}
|
||||
|
||||
// Set references
|
||||
if (input) {
|
||||
;(input as any)._widget = mockReferences.widget
|
||||
;(input as any)._connection = mockReferences.connection
|
||||
;(input as InputWithWidget)._widget = mockReferences.widget
|
||||
;(input as InputWithWidget)._connection = mockReferences.connection
|
||||
}
|
||||
if (output) {
|
||||
;(input as any)._connection = mockReferences.connection
|
||||
;(input as InputWithWidget)._connection = mockReferences.connection
|
||||
}
|
||||
|
||||
// Verify references are set
|
||||
expect((input as any)?._widget).toBe(mockReferences.widget)
|
||||
expect((input as any)?._connection).toBe(mockReferences.connection)
|
||||
expect((input as InputWithWidget)?._widget).toBe(mockReferences.widget)
|
||||
expect((input as InputWithWidget)?._connection).toBe(
|
||||
mockReferences.connection
|
||||
)
|
||||
|
||||
// Simulate proper cleanup (what onRemoved should do)
|
||||
subgraphNode.onRemoved()
|
||||
|
||||
// Input-specific listeners should be cleaned up (this works)
|
||||
if (input && '_listenerController' in input) {
|
||||
expect((input as any)._listenerController?.signal.aborted).toBe(true)
|
||||
expect(
|
||||
(input as InputWithWidget)._listenerController?.signal.aborted
|
||||
).toBe(true)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { SubgraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput'
|
||||
|
||||
import { subgraphTest } from './__fixtures__/subgraphFixtures'
|
||||
import {
|
||||
@@ -531,7 +532,7 @@ describe.skip('SubgraphNode Cleanup', () => {
|
||||
|
||||
// Now trigger an event - only node1 should respond
|
||||
subgraph.events.dispatch('input-added', {
|
||||
input: { name: 'test', type: 'number', id: 'test-id' } as any
|
||||
input: { name: 'test', type: 'number', id: 'test-id' } as SubgraphInput
|
||||
})
|
||||
|
||||
// Only node1 should have added an input
|
||||
@@ -558,7 +559,7 @@ describe.skip('SubgraphNode Cleanup', () => {
|
||||
|
||||
// Trigger an event - no nodes should respond
|
||||
subgraph.events.dispatch('input-added', {
|
||||
input: { name: 'test', type: 'number', id: 'test-id' } as any
|
||||
input: { name: 'test', type: 'number', id: 'test-id' } as SubgraphInput
|
||||
})
|
||||
|
||||
// Without cleanup: all 3 removed nodes would have added an input
|
||||
|
||||
@@ -3,12 +3,18 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { LGraphButton } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
|
||||
|
||||
import {
|
||||
createTestSubgraph,
|
||||
createTestSubgraphNode
|
||||
} from './__fixtures__/subgraphHelpers'
|
||||
|
||||
interface MockPointerEvent {
|
||||
canvasX: number
|
||||
canvasY: number
|
||||
}
|
||||
|
||||
describe.skip('SubgraphNode Title Button', () => {
|
||||
describe.skip('Constructor', () => {
|
||||
it('should automatically add enter_subgraph button', () => {
|
||||
@@ -58,7 +64,7 @@ describe.skip('SubgraphNode Title Button', () => {
|
||||
const canvas = {
|
||||
openSubgraph: vi.fn(),
|
||||
dispatch: vi.fn()
|
||||
} as unknown as LGraphCanvas
|
||||
} as Partial<LGraphCanvas> as LGraphCanvas
|
||||
|
||||
subgraphNode.onTitleButtonClick(enterButton, canvas)
|
||||
|
||||
@@ -78,7 +84,7 @@ describe.skip('SubgraphNode Title Button', () => {
|
||||
const canvas = {
|
||||
openSubgraph: vi.fn(),
|
||||
dispatch: vi.fn()
|
||||
} as unknown as LGraphCanvas
|
||||
} as Partial<LGraphCanvas> as LGraphCanvas
|
||||
|
||||
subgraphNode.onTitleButtonClick(customButton, canvas)
|
||||
|
||||
@@ -119,16 +125,16 @@ describe.skip('SubgraphNode Title Button', () => {
|
||||
const canvas = {
|
||||
ctx: {
|
||||
measureText: vi.fn().mockReturnValue({ width: 25 })
|
||||
} as unknown as CanvasRenderingContext2D,
|
||||
} as Partial<CanvasRenderingContext2D> as CanvasRenderingContext2D,
|
||||
openSubgraph: vi.fn(),
|
||||
dispatch: vi.fn()
|
||||
} as unknown as LGraphCanvas
|
||||
} as Partial<LGraphCanvas> as LGraphCanvas
|
||||
|
||||
// Simulate click on the enter button
|
||||
const event = {
|
||||
const event: MockPointerEvent = {
|
||||
canvasX: 275, // Near right edge where button should be
|
||||
canvasY: 80 // In title area
|
||||
} as any
|
||||
}
|
||||
|
||||
// Calculate node-relative position
|
||||
const clickPosRelativeToNode: [number, number] = [
|
||||
@@ -138,7 +144,7 @@ describe.skip('SubgraphNode Title Button', () => {
|
||||
|
||||
// @ts-expect-error onMouseDown possibly undefined
|
||||
const handled = subgraphNode.onMouseDown(
|
||||
event,
|
||||
event as Partial<CanvasPointerEvent> as CanvasPointerEvent,
|
||||
clickPosRelativeToNode,
|
||||
canvas
|
||||
)
|
||||
@@ -156,16 +162,16 @@ describe.skip('SubgraphNode Title Button', () => {
|
||||
const canvas = {
|
||||
ctx: {
|
||||
measureText: vi.fn().mockReturnValue({ width: 25 })
|
||||
} as unknown as CanvasRenderingContext2D,
|
||||
} as Partial<CanvasRenderingContext2D> as CanvasRenderingContext2D,
|
||||
openSubgraph: vi.fn(),
|
||||
dispatch: vi.fn()
|
||||
} as unknown as LGraphCanvas
|
||||
} as Partial<LGraphCanvas> as LGraphCanvas
|
||||
|
||||
// Click in the body of the node, not on button
|
||||
const event = {
|
||||
const event: MockPointerEvent = {
|
||||
canvasX: 200, // Middle of node
|
||||
canvasY: 150 // Body area
|
||||
} as any
|
||||
}
|
||||
|
||||
// Calculate node-relative position
|
||||
const clickPosRelativeToNode: [number, number] = [
|
||||
@@ -175,7 +181,7 @@ describe.skip('SubgraphNode Title Button', () => {
|
||||
|
||||
// @ts-expect-error onMouseDown possibly undefined
|
||||
const handled = subgraphNode.onMouseDown(
|
||||
event,
|
||||
event as Partial<CanvasPointerEvent> as CanvasPointerEvent,
|
||||
clickPosRelativeToNode,
|
||||
canvas
|
||||
)
|
||||
@@ -204,16 +210,16 @@ describe.skip('SubgraphNode Title Button', () => {
|
||||
const canvas = {
|
||||
ctx: {
|
||||
measureText: vi.fn().mockReturnValue({ width: 25 })
|
||||
} as unknown as CanvasRenderingContext2D,
|
||||
} as Partial<CanvasRenderingContext2D> as CanvasRenderingContext2D,
|
||||
openSubgraph: vi.fn(),
|
||||
dispatch: vi.fn()
|
||||
} as unknown as LGraphCanvas
|
||||
} as Partial<LGraphCanvas> as LGraphCanvas
|
||||
|
||||
// Try to click on where the button would be
|
||||
const event = {
|
||||
const event: MockPointerEvent = {
|
||||
canvasX: 275,
|
||||
canvasY: 80
|
||||
} as any
|
||||
}
|
||||
|
||||
const clickPosRelativeToNode: [number, number] = [
|
||||
275 - subgraphNode.pos[0], // 175
|
||||
@@ -222,7 +228,7 @@ describe.skip('SubgraphNode Title Button', () => {
|
||||
|
||||
// @ts-expect-error onMouseDown possibly undefined
|
||||
const handled = subgraphNode.onMouseDown(
|
||||
event,
|
||||
event as Partial<CanvasPointerEvent> as CanvasPointerEvent,
|
||||
clickPosRelativeToNode,
|
||||
canvas
|
||||
)
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
// TODO: Fix these tests after migration
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { DefaultConnectionColors } from '@/lib/litegraph/src/interfaces'
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { createTestSubgraph } from './__fixtures__/subgraphHelpers'
|
||||
|
||||
interface MockColorContext {
|
||||
defaultInputColor: string
|
||||
defaultOutputColor: string
|
||||
getConnectedColor: ReturnType<typeof vi.fn>
|
||||
getDisconnectedColor: ReturnType<typeof vi.fn>
|
||||
}
|
||||
|
||||
describe.skip('SubgraphSlot visual feedback', () => {
|
||||
let mockCtx: CanvasRenderingContext2D
|
||||
let mockColorContext: any
|
||||
let mockColorContext: MockColorContext
|
||||
let globalAlphaValues: number[]
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -34,7 +42,8 @@ describe.skip('SubgraphSlot visual feedback', () => {
|
||||
rect: vi.fn(),
|
||||
fillText: vi.fn()
|
||||
}
|
||||
mockCtx = mockContext as unknown as CanvasRenderingContext2D
|
||||
mockCtx =
|
||||
mockContext as Partial<CanvasRenderingContext2D> as CanvasRenderingContext2D
|
||||
|
||||
// Create a mock color context
|
||||
mockColorContext = {
|
||||
@@ -42,7 +51,7 @@ describe.skip('SubgraphSlot visual feedback', () => {
|
||||
defaultOutputColor: '#00FF00',
|
||||
getConnectedColor: vi.fn().mockReturnValue('#0000FF'),
|
||||
getDisconnectedColor: vi.fn().mockReturnValue('#AAAAAA')
|
||||
}
|
||||
} as Partial<MockColorContext> as MockColorContext
|
||||
})
|
||||
|
||||
it('should render SubgraphInput slots with full opacity when dragging from compatible slot', () => {
|
||||
@@ -60,7 +69,8 @@ describe.skip('SubgraphSlot visual feedback', () => {
|
||||
// Draw the slot with a compatible fromSlot
|
||||
subgraphInput.draw({
|
||||
ctx: mockCtx,
|
||||
colorContext: mockColorContext,
|
||||
colorContext:
|
||||
mockColorContext as Partial<DefaultConnectionColors> as DefaultConnectionColors,
|
||||
fromSlot: nodeInput,
|
||||
editorAlpha: 1
|
||||
})
|
||||
@@ -80,7 +90,8 @@ describe.skip('SubgraphSlot visual feedback', () => {
|
||||
// Draw subgraphInput2 while dragging from subgraphInput1 (incompatible - both are outputs inside subgraph)
|
||||
subgraphInput2.draw({
|
||||
ctx: mockCtx,
|
||||
colorContext: mockColorContext,
|
||||
colorContext:
|
||||
mockColorContext as Partial<DefaultConnectionColors> as DefaultConnectionColors,
|
||||
fromSlot: subgraphInput1,
|
||||
editorAlpha: 1
|
||||
})
|
||||
@@ -105,7 +116,8 @@ describe.skip('SubgraphSlot visual feedback', () => {
|
||||
// Draw the slot with a compatible fromSlot
|
||||
subgraphOutput.draw({
|
||||
ctx: mockCtx,
|
||||
colorContext: mockColorContext,
|
||||
colorContext:
|
||||
mockColorContext as Partial<DefaultConnectionColors> as DefaultConnectionColors,
|
||||
fromSlot: nodeOutput,
|
||||
editorAlpha: 1
|
||||
})
|
||||
@@ -125,7 +137,8 @@ describe.skip('SubgraphSlot visual feedback', () => {
|
||||
// Draw subgraphOutput2 while dragging from subgraphOutput1 (incompatible - both are inputs inside subgraph)
|
||||
subgraphOutput2.draw({
|
||||
ctx: mockCtx,
|
||||
colorContext: mockColorContext,
|
||||
colorContext:
|
||||
mockColorContext as Partial<DefaultConnectionColors> as DefaultConnectionColors,
|
||||
fromSlot: subgraphOutput1,
|
||||
editorAlpha: 1
|
||||
})
|
||||
@@ -170,7 +183,8 @@ describe.skip('SubgraphSlot visual feedback', () => {
|
||||
// Draw the SubgraphOutput slot while dragging from a node output with incompatible type
|
||||
subgraphOutput.draw({
|
||||
ctx: mockCtx,
|
||||
colorContext: mockColorContext,
|
||||
colorContext:
|
||||
mockColorContext as Partial<DefaultConnectionColors> as DefaultConnectionColors,
|
||||
fromSlot: nodeStringOutput,
|
||||
editorAlpha: 1
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
function createNodeWithWidget(
|
||||
title: string,
|
||||
widgetType: TWidgetType = 'number',
|
||||
widgetValue: any = 42,
|
||||
widgetValue: unknown = 42,
|
||||
slotType: ISlotType = 'number',
|
||||
tooltip?: string
|
||||
) {
|
||||
|
||||
@@ -67,7 +67,7 @@ const MOCK_ASSETS = {
|
||||
} as const
|
||||
|
||||
// Helper functions
|
||||
function mockApiResponse(assets: any[], options = {}) {
|
||||
function mockApiResponse(assets: unknown[], options = {}) {
|
||||
const response = {
|
||||
assets,
|
||||
total: assets.length,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
useSettingStore
|
||||
} from '@/platform/settings/settingStore'
|
||||
import type { SettingParams } from '@/platform/settings/types'
|
||||
import type { Settings } from '@/schemas/apiSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
@@ -45,7 +46,7 @@ describe('useSettingStore', () => {
|
||||
describe('loadSettingValues', () => {
|
||||
it('should load settings from API', async () => {
|
||||
const mockSettings = { 'test.setting': 'value' }
|
||||
vi.mocked(api.getSettings).mockResolvedValue(mockSettings as any)
|
||||
vi.mocked(api.getSettings).mockResolvedValue(mockSettings as Settings)
|
||||
|
||||
await store.loadSettingValues()
|
||||
|
||||
|
||||
@@ -37,6 +37,13 @@ export interface SurveyResponses {
|
||||
making?: string[]
|
||||
}
|
||||
|
||||
export interface SurveyResponsesNormalized extends SurveyResponses {
|
||||
industry_normalized?: string
|
||||
industry_raw?: string
|
||||
useCase_normalized?: string
|
||||
useCase_raw?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Run button tracking properties
|
||||
*/
|
||||
|
||||
@@ -328,9 +328,9 @@ describe('normalizeIndustry', () => {
|
||||
})
|
||||
|
||||
it('should handle null and invalid inputs', () => {
|
||||
expect(normalizeIndustry(null as any)).toBe('Other / Undefined')
|
||||
expect(normalizeIndustry(undefined as any)).toBe('Other / Undefined')
|
||||
expect(normalizeIndustry(123 as any)).toBe('Other / Undefined')
|
||||
expect(normalizeIndustry(null)).toBe('Other / Undefined')
|
||||
expect(normalizeIndustry(undefined)).toBe('Other / Undefined')
|
||||
expect(normalizeIndustry(123)).toBe('Other / Undefined')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -508,7 +508,7 @@ describe('normalizeUseCase', () => {
|
||||
expect(normalizeUseCase('none')).toBe('Other / Undefined')
|
||||
expect(normalizeUseCase('undefined')).toBe('Other / Undefined')
|
||||
expect(normalizeUseCase('')).toBe('Other / Undefined')
|
||||
expect(normalizeUseCase(null as any)).toBe('Other / Undefined')
|
||||
expect(normalizeUseCase(null)).toBe('Other / Undefined')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* Uses Fuse.js for fuzzy matching against category keywords.
|
||||
*/
|
||||
import Fuse from 'fuse.js'
|
||||
import type { SurveyResponses, SurveyResponsesNormalized } from '../types'
|
||||
|
||||
interface CategoryMapping {
|
||||
name: string
|
||||
@@ -583,21 +584,19 @@ export function normalizeUseCase(rawUseCase: unknown): string {
|
||||
* Apply normalization to survey responses
|
||||
* Creates both normalized and raw versions of responses
|
||||
*/
|
||||
export function normalizeSurveyResponses(responses: {
|
||||
industry?: string
|
||||
useCase?: string
|
||||
[key: string]: any
|
||||
}) {
|
||||
const normalized = { ...responses }
|
||||
export function normalizeSurveyResponses(
|
||||
responses: SurveyResponses
|
||||
): SurveyResponsesNormalized {
|
||||
const normalized: SurveyResponsesNormalized = { ...responses }
|
||||
|
||||
// Normalize industry
|
||||
if (responses.industry) {
|
||||
if ('industry' in responses && typeof responses.industry === 'string') {
|
||||
normalized.industry_normalized = normalizeIndustry(responses.industry)
|
||||
normalized.industry_raw = responses.industry
|
||||
}
|
||||
|
||||
// Normalize use case
|
||||
if (responses.useCase) {
|
||||
if ('useCase' in responses && typeof responses.useCase === 'string') {
|
||||
normalized.useCase_normalized = normalizeUseCase(responses.useCase)
|
||||
normalized.useCase_raw = responses.useCase
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user