mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
*PR Created by the Glary-Bot Agent* --- ## Summary - Replace all `as unknown as Type` assertions in 59 unit test files with type-safe `@total-typescript/shoehorn` functions - Use `fromPartial<Type>()` for partial mock objects where deep-partial type-checks (21 files) - Use `fromAny<Type>()` for fundamentally incompatible types: null, undefined, primitives, variables, class expressions, and mocks with test-specific extra properties that `PartialDeepObject` rejects (remaining files) - All explicit type parameters preserved so TypeScript return types are correct - Browser test `.spec.ts` files excluded (shoehorn unavailable in `page.evaluate` browser context) ## Verification - `pnpm typecheck` ✅ - `pnpm lint` ✅ - `pnpm format` ✅ - Pre-commit hooks passed (format + oxlint + eslint + typecheck) - Migrated test files verified passing (ran representative subset) - No test behavior changes — only type assertion syntax changed - No UI changes — screenshots not applicable ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10761-test-migrate-as-unknown-as-to-total-typescript-shoehorn-3336d73d365081f6b8adc44db5dcc380) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: Amp <amp@ampcode.com>
211 lines
5.9 KiB
TypeScript
211 lines
5.9 KiB
TypeScript
import { createTestingPinia } from '@pinia/testing'
|
|
import { fromPartial } from '@total-typescript/shoehorn'
|
|
import { mount } from '@vue/test-utils'
|
|
import { setActivePinia } from 'pinia'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import DomWidgets from '@/components/graph/DomWidgets.vue'
|
|
import { Rectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
|
|
import { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
|
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
|
import type { BaseDOMWidget } from '@/scripts/domWidget'
|
|
import { useDomWidgetStore } from '@/stores/domWidgetStore'
|
|
|
|
type TestWidget = BaseDOMWidget<object | string>
|
|
|
|
function createNode(
|
|
graph: LGraph,
|
|
id: number,
|
|
title: string,
|
|
pos: [number, number]
|
|
) {
|
|
const node = new LGraphNode(title)
|
|
node.id = id
|
|
node.pos = [...pos]
|
|
node.size = [240, 120]
|
|
graph.add(node)
|
|
return node
|
|
}
|
|
|
|
function createWidget(id: string, node: LGraphNode, y = 12): TestWidget {
|
|
return fromPartial<TestWidget>({
|
|
id,
|
|
node,
|
|
name: 'test_widget',
|
|
type: 'custom',
|
|
value: '',
|
|
options: {},
|
|
y,
|
|
width: 120,
|
|
computedHeight: 40,
|
|
margin: 10,
|
|
isVisible: () => true
|
|
})
|
|
}
|
|
|
|
function createCanvas(graph: LGraph): LGraphCanvas {
|
|
return fromPartial<LGraphCanvas>({
|
|
graph,
|
|
low_quality: false,
|
|
read_only: false,
|
|
isNodeVisible: vi.fn(() => true)
|
|
})
|
|
}
|
|
|
|
function drawFrame(canvas: LGraphCanvas) {
|
|
canvas.onDrawForeground?.({} as CanvasRenderingContext2D, new Rectangle())
|
|
}
|
|
|
|
describe('DomWidgets transition grace characterization', () => {
|
|
beforeEach(() => {
|
|
setActivePinia(createTestingPinia({ stubActions: false }))
|
|
})
|
|
|
|
it('applies transition grace for exactly one frame when override exists but is not active', () => {
|
|
const canvasStore = useCanvasStore()
|
|
const domWidgetStore = useDomWidgetStore()
|
|
|
|
const graphA = new LGraph()
|
|
const graphB = new LGraph()
|
|
const interiorNode = createNode(graphA, 1, 'interior', [100, 200])
|
|
const overrideNode = createNode(graphB, 2, 'override', [600, 700])
|
|
|
|
const widget = createWidget('widget-transition', interiorNode, 14)
|
|
const overrideWidget = createWidget('override-widget', overrideNode, 22)
|
|
|
|
domWidgetStore.registerWidget(widget)
|
|
domWidgetStore.setPositionOverride(widget.id, {
|
|
node: overrideNode,
|
|
widget: overrideWidget
|
|
})
|
|
domWidgetStore.deactivateWidget(widget.id)
|
|
|
|
const widgetState = domWidgetStore.widgetStates.get(widget.id)
|
|
if (!widgetState) throw new Error('Widget state not registered')
|
|
widgetState.visible = true
|
|
widgetState.pos = [321, 654]
|
|
|
|
const canvas = createCanvas(graphA)
|
|
canvasStore.canvas = canvas
|
|
|
|
mount(DomWidgets, {
|
|
global: {
|
|
stubs: {
|
|
DomWidget: true
|
|
}
|
|
}
|
|
})
|
|
|
|
drawFrame(canvas)
|
|
expect(widgetState.visible).toBe(true)
|
|
expect(widgetState.pos).toEqual([321, 654])
|
|
|
|
drawFrame(canvas)
|
|
expect(widgetState.visible).toBe(false)
|
|
})
|
|
|
|
it('uses override positioning while override node is in current graph even when widget is inactive', () => {
|
|
const canvasStore = useCanvasStore()
|
|
const domWidgetStore = useDomWidgetStore()
|
|
|
|
const graphA = new LGraph()
|
|
const graphB = new LGraph()
|
|
const interiorNode = createNode(graphA, 1, 'interior', [10, 20])
|
|
const overrideNode = createNode(graphB, 2, 'override', [300, 400])
|
|
|
|
const widget = createWidget('widget-override-active', interiorNode, 8)
|
|
const overrideWidget = createWidget(
|
|
'override-position-source',
|
|
overrideNode,
|
|
18
|
|
)
|
|
|
|
domWidgetStore.registerWidget(widget)
|
|
domWidgetStore.setPositionOverride(widget.id, {
|
|
node: overrideNode,
|
|
widget: overrideWidget
|
|
})
|
|
domWidgetStore.deactivateWidget(widget.id)
|
|
|
|
const widgetState = domWidgetStore.widgetStates.get(widget.id)
|
|
if (!widgetState) throw new Error('Widget state not registered')
|
|
|
|
const canvas = createCanvas(graphB)
|
|
canvasStore.canvas = canvas
|
|
|
|
mount(DomWidgets, {
|
|
global: {
|
|
stubs: {
|
|
DomWidget: true
|
|
}
|
|
}
|
|
})
|
|
|
|
drawFrame(canvas)
|
|
|
|
expect(widgetState.visible).toBe(true)
|
|
expect(widgetState.pos).toEqual([310, 428])
|
|
})
|
|
|
|
it('cleans orphaned transition-grace ids after widget removal', () => {
|
|
const canvasStore = useCanvasStore()
|
|
const domWidgetStore = useDomWidgetStore()
|
|
|
|
const graphA = new LGraph()
|
|
const graphB = new LGraph()
|
|
const interiorNode = createNode(graphA, 1, 'interior', [0, 0])
|
|
const overrideNode = createNode(graphB, 2, 'override', [200, 200])
|
|
|
|
const canvas = createCanvas(graphA)
|
|
canvasStore.canvas = canvas
|
|
|
|
mount(DomWidgets, {
|
|
global: {
|
|
stubs: {
|
|
DomWidget: true
|
|
}
|
|
}
|
|
})
|
|
|
|
const oldWidget = createWidget('shared-widget-id', interiorNode, 10)
|
|
const overrideWidget = createWidget(
|
|
'shared-override-widget',
|
|
overrideNode,
|
|
14
|
|
)
|
|
|
|
domWidgetStore.registerWidget(oldWidget)
|
|
domWidgetStore.setPositionOverride(oldWidget.id, {
|
|
node: overrideNode,
|
|
widget: overrideWidget
|
|
})
|
|
domWidgetStore.deactivateWidget(oldWidget.id)
|
|
|
|
drawFrame(canvas)
|
|
domWidgetStore.unregisterWidget(oldWidget.id)
|
|
|
|
drawFrame(canvas)
|
|
|
|
const replacementWidget = createWidget('shared-widget-id', interiorNode, 10)
|
|
domWidgetStore.registerWidget(replacementWidget)
|
|
domWidgetStore.setPositionOverride(replacementWidget.id, {
|
|
node: overrideNode,
|
|
widget: overrideWidget
|
|
})
|
|
domWidgetStore.deactivateWidget(replacementWidget.id)
|
|
|
|
const replacementState = domWidgetStore.widgetStates.get(
|
|
replacementWidget.id
|
|
)
|
|
if (!replacementState) throw new Error('Replacement widget missing state')
|
|
replacementState.visible = true
|
|
replacementState.pos = [999, 999]
|
|
|
|
drawFrame(canvas)
|
|
|
|
expect(replacementState.visible).toBe(true)
|
|
expect(replacementState.pos).toEqual([999, 999])
|
|
})
|
|
})
|