Files
ComfyUI_frontend/src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts
Alexander Brown 661e3d7949 test: migrate as unknown as to @total-typescript/shoehorn (#10761)
*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>
2026-03-30 19:20:18 -07:00

246 lines
7.8 KiB
TypeScript

import { createTestingPinia } from '@pinia/testing'
import { fromAny } from '@total-typescript/shoehorn'
import { mount } from '@vue/test-utils'
import PrimeVue from 'primevue/config'
import { describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'
import type { SwapNodeGroup } from '@/components/rightSidePanel/errors/useErrorGroups'
import type { MissingNodeType } from '@/types/comfy'
import SwapNodeGroupRow from './SwapNodeGroupRow.vue'
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: {
rightSidePanel: {
locateNode: 'Locate Node',
missingNodePacks: {
collapse: 'Collapse',
expand: 'Expand'
}
},
nodeReplacement: {
willBeReplacedBy: 'This node will be replaced by:',
replaceNode: 'Replace Node',
unknownNode: 'Unknown'
}
}
},
missingWarn: false,
fallbackWarn: false
})
function makeGroup(overrides: Partial<SwapNodeGroup> = {}): SwapNodeGroup {
return {
type: 'OldNodeType',
newNodeId: 'NewNodeType',
nodeTypes: [
{ type: 'OldNodeType', nodeId: '1', isReplaceable: true },
{ type: 'OldNodeType', nodeId: '2', isReplaceable: true }
],
...overrides
}
}
function mountRow(
props: Partial<{
group: SwapNodeGroup
showNodeIdBadge: boolean
}> = {}
) {
return mount(SwapNodeGroupRow, {
props: {
group: makeGroup(),
showNodeIdBadge: false,
...props
},
global: {
plugins: [createTestingPinia({ createSpy: vi.fn }), PrimeVue, i18n],
stubs: {
TransitionCollapse: { template: '<div><slot /></div>' }
}
}
})
}
describe('SwapNodeGroupRow', () => {
describe('Basic Rendering', () => {
it('renders the group type name', () => {
const wrapper = mountRow()
expect(wrapper.text()).toContain('OldNodeType')
})
it('renders node count in parentheses', () => {
const wrapper = mountRow()
expect(wrapper.text()).toContain('(2)')
})
it('renders node count of 5 for 5 nodeTypes', () => {
const wrapper = mountRow({
group: makeGroup({
nodeTypes: Array.from({ length: 5 }, (_, i) => ({
type: 'OldNodeType',
nodeId: String(i),
isReplaceable: true
}))
})
})
expect(wrapper.text()).toContain('(5)')
})
it('renders the replacement target name', () => {
const wrapper = mountRow()
expect(wrapper.text()).toContain('NewNodeType')
})
it('shows "Unknown" when newNodeId is undefined', () => {
const wrapper = mountRow({
group: makeGroup({ newNodeId: undefined })
})
expect(wrapper.text()).toContain('Unknown')
})
it('renders "Replace Node" button', () => {
const wrapper = mountRow()
expect(wrapper.text()).toContain('Replace Node')
})
})
describe('Expand / Collapse', () => {
it('starts collapsed — node list not visible', () => {
const wrapper = mountRow({ showNodeIdBadge: true })
expect(wrapper.text()).not.toContain('#1')
})
it('expands when chevron is clicked', async () => {
const wrapper = mountRow({ showNodeIdBadge: true })
await wrapper.get('button[aria-label="Expand"]').trigger('click')
expect(wrapper.text()).toContain('#1')
expect(wrapper.text()).toContain('#2')
})
it('collapses when chevron is clicked again', async () => {
const wrapper = mountRow({ showNodeIdBadge: true })
await wrapper.get('button[aria-label="Expand"]').trigger('click')
expect(wrapper.text()).toContain('#1')
await wrapper.get('button[aria-label="Collapse"]').trigger('click')
expect(wrapper.text()).not.toContain('#1')
})
it('updates the toggle control state when expanded', async () => {
const wrapper = mountRow()
expect(wrapper.find('button[aria-label="Expand"]').exists()).toBe(true)
await wrapper.get('button[aria-label="Expand"]').trigger('click')
expect(wrapper.find('button[aria-label="Collapse"]').exists()).toBe(true)
})
})
describe('Node Type List (Expanded)', () => {
async function expand(wrapper: ReturnType<typeof mountRow>) {
await wrapper.get('button[aria-label="Expand"]').trigger('click')
}
it('renders all nodeTypes when expanded', async () => {
const wrapper = mountRow({
group: makeGroup({
nodeTypes: [
{ type: 'OldNodeType', nodeId: '10', isReplaceable: true },
{ type: 'OldNodeType', nodeId: '20', isReplaceable: true },
{ type: 'OldNodeType', nodeId: '30', isReplaceable: true }
]
}),
showNodeIdBadge: true
})
await expand(wrapper)
expect(wrapper.text()).toContain('#10')
expect(wrapper.text()).toContain('#20')
expect(wrapper.text()).toContain('#30')
})
it('shows nodeId badge when showNodeIdBadge is true', async () => {
const wrapper = mountRow({ showNodeIdBadge: true })
await expand(wrapper)
expect(wrapper.text()).toContain('#1')
expect(wrapper.text()).toContain('#2')
})
it('hides nodeId badge when showNodeIdBadge is false', async () => {
const wrapper = mountRow({ showNodeIdBadge: false })
await expand(wrapper)
expect(wrapper.text()).not.toContain('#1')
expect(wrapper.text()).not.toContain('#2')
})
it('renders Locate button for each nodeType with nodeId', async () => {
const wrapper = mountRow({ showNodeIdBadge: true })
await expand(wrapper)
expect(wrapper.findAll('button[aria-label="Locate Node"]')).toHaveLength(
2
)
})
it('does not render Locate button for nodeTypes without nodeId', async () => {
const wrapper = mountRow({
group: makeGroup({
// Intentionally omits nodeId to test graceful handling of incomplete node data
nodeTypes: fromAny<MissingNodeType[], unknown>([
{ type: 'NoIdNode', isReplaceable: true }
])
})
})
await expand(wrapper)
expect(wrapper.find('button[aria-label="Locate Node"]').exists()).toBe(
false
)
})
})
describe('Events', () => {
it('emits locate-node with correct nodeId', async () => {
const wrapper = mountRow({ showNodeIdBadge: true })
await wrapper.get('button[aria-label="Expand"]').trigger('click')
const locateBtns = wrapper.findAll('button[aria-label="Locate Node"]')
await locateBtns[0].trigger('click')
expect(wrapper.emitted('locate-node')).toBeTruthy()
expect(wrapper.emitted('locate-node')?.[0]).toEqual(['1'])
await locateBtns[1].trigger('click')
expect(wrapper.emitted('locate-node')?.[1]).toEqual(['2'])
})
it('emits replace with group when Replace button is clicked', async () => {
const group = makeGroup()
const wrapper = mountRow({ group })
const replaceBtn = wrapper
.findAll('button')
.find((b) => b.text().includes('Replace Node'))
if (!replaceBtn) throw new Error('Replace button not found')
await replaceBtn.trigger('click')
expect(wrapper.emitted('replace')).toBeTruthy()
expect(wrapper.emitted('replace')?.[0][0]).toEqual(group)
})
})
describe('Edge Cases', () => {
it('handles empty nodeTypes array', () => {
const wrapper = mountRow({
group: makeGroup({ nodeTypes: [] })
})
expect(wrapper.text()).toContain('(0)')
})
it('handles string nodeType entries', async () => {
const wrapper = mountRow({
group: makeGroup({
// Intentionally uses a plain string entry to test legacy node type handling
nodeTypes: fromAny<MissingNodeType[], unknown>(['StringType'])
})
})
await wrapper.get('button[aria-label="Expand"]').trigger('click')
expect(wrapper.text()).toContain('StringType')
})
})
})