mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-19 22:09:37 +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>
206 lines
5.5 KiB
TypeScript
206 lines
5.5 KiB
TypeScript
import { createTestingPinia } from '@pinia/testing'
|
|
import { fromAny } from '@total-typescript/shoehorn'
|
|
import { mount } from '@vue/test-utils'
|
|
import { setActivePinia } from 'pinia'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { createI18n } from 'vue-i18n'
|
|
|
|
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
|
import WidgetItem from './WidgetItem.vue'
|
|
|
|
const { mockGetInputSpecForWidget, StubWidgetComponent } = vi.hoisted(() => ({
|
|
mockGetInputSpecForWidget: vi.fn(),
|
|
StubWidgetComponent: {
|
|
name: 'StubWidget',
|
|
props: ['widget', 'modelValue', 'nodeId', 'nodeType'],
|
|
template: '<div class="stub-widget" />'
|
|
}
|
|
}))
|
|
|
|
vi.mock('@/stores/nodeDefStore', () => ({
|
|
useNodeDefStore: () => ({
|
|
getInputSpecForWidget: mockGetInputSpecForWidget
|
|
})
|
|
}))
|
|
|
|
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
|
useCanvasStore: () => ({
|
|
canvas: { setDirty: vi.fn() }
|
|
})
|
|
}))
|
|
|
|
vi.mock('@/stores/workspace/favoritedWidgetsStore', () => ({
|
|
useFavoritedWidgetsStore: () => ({
|
|
isFavorited: vi.fn().mockReturnValue(false),
|
|
toggleFavorite: vi.fn()
|
|
})
|
|
}))
|
|
|
|
vi.mock('@/composables/graph/useGraphNodeManager', () => ({
|
|
getControlWidget: vi.fn(() => undefined)
|
|
}))
|
|
|
|
vi.mock('@/core/graph/subgraph/resolvePromotedWidgetSource', () => ({
|
|
resolvePromotedWidgetSource: vi.fn(() => undefined)
|
|
}))
|
|
|
|
vi.mock(
|
|
'@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry',
|
|
() => ({
|
|
getComponent: () => StubWidgetComponent,
|
|
shouldExpand: () => false
|
|
})
|
|
)
|
|
|
|
vi.mock(
|
|
'@/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue',
|
|
() => ({
|
|
default: StubWidgetComponent
|
|
})
|
|
)
|
|
|
|
const i18n = createI18n({
|
|
legacy: false,
|
|
locale: 'en',
|
|
messages: {
|
|
en: {
|
|
rightSidePanel: {
|
|
fallbackNodeTitle: 'Untitled'
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
function createMockNode(overrides: Partial<LGraphNode> = {}): LGraphNode {
|
|
return fromAny<LGraphNode, unknown>({
|
|
id: 1,
|
|
type: 'TestNode',
|
|
isSubgraphNode: () => false,
|
|
graph: { rootGraph: { id: 'test-graph-id' } },
|
|
...overrides
|
|
})
|
|
}
|
|
|
|
function createMockWidget(overrides: Partial<IBaseWidget> = {}): IBaseWidget {
|
|
return {
|
|
name: 'test_widget',
|
|
type: 'combo',
|
|
value: 'option_a',
|
|
y: 0,
|
|
options: {
|
|
values: ['option_a', 'option_b', 'option_c']
|
|
},
|
|
...overrides
|
|
} as IBaseWidget
|
|
}
|
|
|
|
/**
|
|
* Creates a mock PromotedWidgetView that mirrors the real class:
|
|
* properties like name, type, value, options are prototype getters,
|
|
* NOT own properties — so object spread loses them.
|
|
*/
|
|
function createMockPromotedWidgetView(
|
|
sourceOptions: IBaseWidget['options'] = {
|
|
values: ['model_a.safetensors', 'model_b.safetensors']
|
|
}
|
|
): IBaseWidget {
|
|
class MockPromotedWidgetView {
|
|
readonly sourceNodeId = '42'
|
|
readonly sourceWidgetName = 'ckpt_name'
|
|
readonly serialize = false
|
|
|
|
get name(): string {
|
|
return 'ckpt_name'
|
|
}
|
|
get type(): string {
|
|
return 'combo'
|
|
}
|
|
get value(): unknown {
|
|
return 'model_a.safetensors'
|
|
}
|
|
get options(): IBaseWidget['options'] {
|
|
return sourceOptions
|
|
}
|
|
get label(): string | undefined {
|
|
return undefined
|
|
}
|
|
get y(): number {
|
|
return 0
|
|
}
|
|
}
|
|
return fromAny<IBaseWidget, unknown>(new MockPromotedWidgetView())
|
|
}
|
|
|
|
function mountWidgetItem(
|
|
widget: IBaseWidget,
|
|
node: LGraphNode = createMockNode()
|
|
) {
|
|
return mount(WidgetItem, {
|
|
props: { widget, node },
|
|
global: {
|
|
plugins: [i18n],
|
|
stubs: {
|
|
EditableText: { template: '<span />' },
|
|
WidgetActions: { template: '<span />' }
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
describe('WidgetItem', () => {
|
|
beforeEach(() => {
|
|
setActivePinia(createTestingPinia({ stubActions: false }))
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('promoted widget options', () => {
|
|
it('passes options from a regular widget to the widget component', () => {
|
|
const widget = createMockWidget({
|
|
options: { values: ['a', 'b', 'c'] }
|
|
})
|
|
const wrapper = mountWidgetItem(widget)
|
|
const stub = wrapper.findComponent(StubWidgetComponent)
|
|
|
|
expect(stub.props('widget').options).toEqual({
|
|
values: ['a', 'b', 'c']
|
|
})
|
|
})
|
|
|
|
it('passes options from a PromotedWidgetView to the widget component', () => {
|
|
const expectedOptions = {
|
|
values: ['model_a.safetensors', 'model_b.safetensors']
|
|
}
|
|
const widget = createMockPromotedWidgetView(expectedOptions)
|
|
const wrapper = mountWidgetItem(widget)
|
|
const stub = wrapper.findComponent(StubWidgetComponent)
|
|
|
|
expect(stub.props('widget').options).toEqual(expectedOptions)
|
|
})
|
|
|
|
it('passes type from a PromotedWidgetView to the widget component', () => {
|
|
const widget = createMockPromotedWidgetView()
|
|
const wrapper = mountWidgetItem(widget)
|
|
const stub = wrapper.findComponent(StubWidgetComponent)
|
|
|
|
expect(stub.props('widget').type).toBe('combo')
|
|
})
|
|
|
|
it('passes name from a PromotedWidgetView to the widget component', () => {
|
|
const widget = createMockPromotedWidgetView()
|
|
const wrapper = mountWidgetItem(widget)
|
|
const stub = wrapper.findComponent(StubWidgetComponent)
|
|
|
|
expect(stub.props('widget').name).toBe('ckpt_name')
|
|
})
|
|
|
|
it('passes value from a PromotedWidgetView to the widget component', () => {
|
|
const widget = createMockPromotedWidgetView()
|
|
const wrapper = mountWidgetItem(widget)
|
|
const stub = wrapper.findComponent(StubWidgetComponent)
|
|
|
|
expect(stub.props('widget').value).toBe('model_a.safetensors')
|
|
})
|
|
})
|
|
})
|