Files
ComfyUI_frontend/src/renderer/extensions/linearMode/LinearWelcome.test.ts
Alexander Brown 3043b181d7 refactor: extract composables from VTU holdout components, complete VTL migration (#10966)
## Summary

Extract internal logic from the 2 remaining VTU holdout components into
composables, enabling full VTL migration.

## Changes

- **What**: Extract `useProcessedWidgets` from `NodeWidgets.vue`
(486→135 LOC) and `useWidgetSelectItems`/`useWidgetSelectActions` from
`WidgetSelectDropdown.vue` (563→170 LOC). Rewrite both component test
files as composable unit tests + slim behavioral VTL tests. Remove
`@vue/test-utils` devDependency.
- **Dependencies**: Removes `@vue/test-utils`

## Review Focus

- Composable extraction is mechanical — no logic changes, just moving
code into testable units
- `useProcessedWidgets` handles widget deduplication, promotion border
styling, error detection, and identity resolution (~290 LOC)
- `useWidgetSelectItems` handles the full computed chain from widget
values → dropdown items including cloud asset mode and multi-output job
resolution (~350 LOC)
- `useWidgetSelectActions` handles selection resolution and file upload
(~120 LOC)
- 40 new composable-level unit tests replace 13 `wrapper.vm.*` accesses
across the 2 holdout files

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10966-refactor-extract-composables-from-VTU-holdout-components-complete-VTL-migration-33c6d73d36508148a3a4ccf346722d6d)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-04-10 19:04:16 -07:00

84 lines
2.3 KiB
TypeScript

import { render, screen } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'
import LinearWelcome from './LinearWelcome.vue'
const { hasNodes, hasOutputs, enterBuilder } = vi.hoisted(() => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { ref } = require('vue')
return {
hasNodes: ref(false),
hasOutputs: ref(false),
enterBuilder: vi.fn()
}
})
vi.mock('@/composables/useAppMode', () => ({
useAppMode: () => ({ setMode: vi.fn() })
}))
vi.mock('@/composables/useWorkflowTemplateSelectorDialog', () => ({
useWorkflowTemplateSelectorDialog: () => ({ show: vi.fn() })
}))
vi.mock('@/stores/appModeStore', () => ({
useAppModeStore: () => ({
hasNodes,
hasOutputs,
enterBuilder
})
}))
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
useWorkflowStore: () => ({
activeWorkflow: null
})
}))
const i18n = createI18n({ legacy: false, locale: 'en', missingWarn: false })
function renderComponent(
opts: { hasNodes?: boolean; hasOutputs?: boolean } = {}
) {
hasNodes.value = opts.hasNodes ?? false
hasOutputs.value = opts.hasOutputs ?? false
return render(LinearWelcome, {
global: { plugins: [i18n] }
})
}
describe('LinearWelcome', () => {
beforeEach(() => {
hasNodes.value = false
hasOutputs.value = false
vi.clearAllMocks()
})
it('shows empty workflow text when there are no nodes', () => {
renderComponent({ hasNodes: false })
expect(
screen.getByTestId('linear-welcome-empty-workflow')
).toBeInTheDocument()
expect(
screen.queryByTestId('linear-welcome-build-app')
).not.toBeInTheDocument()
})
it('shows build app button when there are nodes but no outputs', () => {
renderComponent({ hasNodes: true, hasOutputs: false })
expect(
screen.queryByTestId('linear-welcome-empty-workflow')
).not.toBeInTheDocument()
expect(screen.getByTestId('linear-welcome-build-app')).toBeInTheDocument()
})
it('clicking build app button calls enterBuilder', async () => {
const user = userEvent.setup()
renderComponent({ hasNodes: true, hasOutputs: false })
await user.click(screen.getByTestId('linear-welcome-build-app'))
expect(enterBuilder).toHaveBeenCalled()
})
})