Road to No explicit any Part 8 (Group 3): Improve type safety in Group 3 test mocks (#8304)

## Summary

- Eliminated all `as unknown as` type assertions from Group 3 test files
- Created reusable factory functions in `litegraphTestUtils.ts` for
better type safety
- Improved test mock composition using `Partial` types with single `as`
casts
- Fixed LGraphNode tests to use proper API methods instead of direct
property assignment

## Changes by Category

### New Factory Functions in `litegraphTestUtils.ts`

- `createMockLGraphNodeWithArrayBoundingRect()` - Creates LGraphNode
with proper boundingRect for position tests
- `createMockFileList()` - Creates mock FileList with proper structure
including `item()` method

### Test File Improvements

**Composables:**
- `useLoad3dDrag.test.ts` - Used `createMockFileList` factory
- `useLoad3dViewer.test.ts` - Created local `MockSceneManager` interface
with proper typing

**LiteGraph Tests:**
- `LGraphNode.test.ts` - Replaced direct `boundingRect` assignments with
`updateArea()` calls
- `LinkConnector.test.ts` - Improved mock composition with proper
Partial types
- `ToOutputRenderLink.test.ts` - Added `MockEvents` interface for
type-safe event mocking
- Updated integration and core tests to use new factory functions

**Extension Tests:**
- `contextMenuFilter.test.ts` - Updated menu factories to accept
`(IContextMenuValue | null)[]`

## Type Safety Improvements

- Zero `as unknown as` instances (was: multiple instances across 17
files)
- All mocks use proper `Partial<T>` composition with single `as T` casts
- Improved IntelliSense and type checking in test files
- Centralized mock creation reduces duplication and improves
maintainability

## Test Plan

-  All TypeScript type checks pass
-  ESLint passes with no new errors  
-  Pre-commit hooks (format, lint, typecheck) all pass
-  Knip unused export check passes
-  No behavioral changes to actual tests (only type improvements)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8304-Road-to-No-explicit-any-Improve-type-safety-in-Group-3-test-mocks-2f36d73d365081ab841de96e5f01306d)
by [Unito](https://www.unito.io)
This commit is contained in:
Johnpaul Chiwetelu
2026-01-26 18:13:18 +01:00
committed by GitHub
parent ba5380395d
commit 29220f6562
17 changed files with 606 additions and 355 deletions

View File

@@ -47,8 +47,9 @@ export class ClipspaceDialog extends ComfyDialog {
if (ClipspaceDialog.instance) {
const self = ClipspaceDialog.instance
// allow reconstruct controls when copying from non-image to image content.
const imgSettings = self.createImgSettings()
const children = $el('div.comfy-modal-content', [
self.createImgSettings(),
...(imgSettings ? [imgSettings] : []),
...self.createButtons()
])
@@ -103,7 +104,7 @@ export class ClipspaceDialog extends ComfyDialog {
return buttons
}
createImgSettings() {
createImgSettings(): HTMLTableElement | null {
if (ComfyApp.clipspace?.imgs) {
const combo_items = []
const imgs = ComfyApp.clipspace.imgs
@@ -167,14 +168,14 @@ export class ClipspaceDialog extends ComfyDialog {
return $el('table', {}, [row1, row2, row3])
} else {
return []
return null
}
}
createImgPreview() {
createImgPreview(): HTMLImageElement | null {
if (ComfyApp.clipspace?.imgs) {
return $el('img', { id: 'clipspace_preview', ondragstart: () => false })
} else return []
} else return null
}
override show() {

View File

@@ -1,6 +1,10 @@
import { describe, expect, it, vi } from 'vitest'
import { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat'
import type {
IContextMenuValue,
LGraphNode
} from '@/lib/litegraph/src/litegraph'
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
/**
@@ -18,11 +22,12 @@ describe('Context Menu Extension Name in Warnings', () => {
// Extension monkey-patches the method
const original = LGraphCanvas.prototype.getCanvasMenuOptions
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args: any[]) {
const items = (original as any).apply(this, args)
items.push({ content: 'My Custom Menu Item', callback: () => {} })
return items
}
LGraphCanvas.prototype.getCanvasMenuOptions =
function (): (IContextMenuValue | null)[] {
const items = original.call(this)
items.push({ content: 'My Custom Menu Item', callback: () => {} })
return items
}
// Clear extension (happens after setup completes)
legacyMenuCompat.setCurrentExtension(null)
@@ -49,8 +54,8 @@ describe('Context Menu Extension Name in Warnings', () => {
// Extension monkey-patches the method
const original = LGraphCanvas.prototype.getNodeMenuOptions
LGraphCanvas.prototype.getNodeMenuOptions = function (...args: any[]) {
const items = (original as any).apply(this, args)
LGraphCanvas.prototype.getNodeMenuOptions = function (node: LGraphNode) {
const items = original.call(this, node)
items.push({ content: 'My Node Menu Item', callback: () => {} })
return items
}

View File

@@ -7,6 +7,10 @@ import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useExtensionService } from '@/services/extensionService'
import { useExtensionStore } from '@/stores/extensionStore'
import type { ComfyExtension } from '@/types/comfy'
import {
createMockCanvas,
createMockLGraphNode
} from '@/utils/__tests__/litegraphTestUtils'
describe('Context Menu Extension API', () => {
let mockCanvas: LGraphCanvas
@@ -35,7 +39,7 @@ describe('Context Menu Extension API', () => {
// Mock extensions
const createCanvasMenuExtension = (
name: string,
items: IContextMenuValue[]
items: (IContextMenuValue | null)[]
): ComfyExtension => ({
name,
getCanvasMenuItems: () => items
@@ -54,16 +58,16 @@ describe('Context Menu Extension API', () => {
extensionStore = useExtensionStore()
extensionService = useExtensionService()
mockCanvas = {
mockCanvas = createMockCanvas({
graph_mouse: [100, 100],
selectedItems: new Set()
} as unknown as LGraphCanvas
})
mockNode = {
mockNode = createMockLGraphNode({
id: 1,
type: 'TestNode',
pos: [0, 0]
} as unknown as LGraphNode
})
})
describe('collectCanvasMenuItems', () => {
@@ -79,7 +83,7 @@ describe('Context Menu Extension API', () => {
const items: IContextMenuValue[] = extensionService
.invokeExtensions('getCanvasMenuItems', mockCanvas)
.flat()
.flat() as IContextMenuValue[]
expect(items).toHaveLength(3)
expect(items[0]).toMatchObject({ content: 'Canvas Item 1' })
@@ -99,7 +103,7 @@ describe('Context Menu Extension API', () => {
]
}
},
null as unknown as IContextMenuValue,
null,
{ content: 'After Separator', callback: () => {} }
])
@@ -107,7 +111,7 @@ describe('Context Menu Extension API', () => {
const items: IContextMenuValue[] = extensionService
.invokeExtensions('getCanvasMenuItems', mockCanvas)
.flat()
.flat() as IContextMenuValue[]
expect(items).toHaveLength(3)
expect(items[0].content).toBe('Menu with Submenu')
@@ -129,7 +133,7 @@ describe('Context Menu Extension API', () => {
const items: IContextMenuValue[] = extensionService
.invokeExtensions('getCanvasMenuItems', mockCanvas)
.flat()
.flat() as IContextMenuValue[]
expect(items).toHaveLength(1)
expect(items[0].content).toBe('Canvas Item 1')
@@ -146,11 +150,11 @@ describe('Context Menu Extension API', () => {
// Collect items multiple times (simulating repeated menu opens)
const items1: IContextMenuValue[] = extensionService
.invokeExtensions('getCanvasMenuItems', mockCanvas)
.flat()
.flat() as IContextMenuValue[]
const items2: IContextMenuValue[] = extensionService
.invokeExtensions('getCanvasMenuItems', mockCanvas)
.flat()
.flat() as IContextMenuValue[]
// Both collections should have the same items (no duplication)
expect(items1).toHaveLength(2)
@@ -180,7 +184,7 @@ describe('Context Menu Extension API', () => {
const items: IContextMenuValue[] = extensionService
.invokeExtensions('getNodeMenuItems', mockNode)
.flat()
.flat() as IContextMenuValue[]
expect(items).toHaveLength(3)
expect(items[0]).toMatchObject({ content: 'Node Item 1' })
@@ -205,7 +209,7 @@ describe('Context Menu Extension API', () => {
const items: IContextMenuValue[] = extensionService
.invokeExtensions('getNodeMenuItems', mockNode)
.flat()
.flat() as IContextMenuValue[]
expect(items[0].content).toBe('Node Menu with Submenu')
expect(items[0].submenu?.options).toHaveLength(2)
@@ -222,7 +226,7 @@ describe('Context Menu Extension API', () => {
const items: IContextMenuValue[] = extensionService
.invokeExtensions('getNodeMenuItems', mockNode)
.flat()
.flat() as IContextMenuValue[]
expect(items).toHaveLength(1)
expect(items[0].content).toBe('Node Item 1')

View File

@@ -32,9 +32,13 @@ import { GroupNodeConfig, GroupNodeHandler } from './groupNode'
const id = 'Comfy.NodeTemplates'
const file = 'comfy.templates.json'
interface NodeTemplate {
name: string
data: string
}
class ManageTemplates extends ComfyDialog {
// @ts-expect-error fixme ts strict error
templates: any[]
templates: NodeTemplate[] = []
draggedEl: HTMLElement | null
saveVisualCue: number | null
emptyImg: HTMLImageElement