chore: snapshot workflow migration progress
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
import { useState } from 'react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { render, screen, waitFor, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
|
||||
import type { RenderTemplate } from '../../api/renderTemplates'
|
||||
import type { WorkflowNodeDefinition, WorkflowParams } from '../../api/workflows'
|
||||
import { WorkflowNodeInspector } from '../../components/workflows/WorkflowNodeInspector'
|
||||
|
||||
const listRenderTemplates = vi.fn<() => Promise<RenderTemplate[]>>()
|
||||
|
||||
vi.mock('../../api/renderTemplates', () => ({
|
||||
listRenderTemplates: () => listRenderTemplates(),
|
||||
}))
|
||||
|
||||
const resolveTemplateDefinition: WorkflowNodeDefinition = {
|
||||
step: 'resolve_template',
|
||||
label: 'Resolve Template',
|
||||
family: 'order_line',
|
||||
module_key: 'rendering.resolve_template',
|
||||
category: 'processing',
|
||||
description: 'Resolve a template for order-line rendering.',
|
||||
node_type: 'processNode',
|
||||
icon: 'layers',
|
||||
defaults: {},
|
||||
fields: [
|
||||
{
|
||||
key: 'template_id_override',
|
||||
label: 'Template Override',
|
||||
type: 'text',
|
||||
description: 'Manual template override.',
|
||||
section: 'General',
|
||||
default: '',
|
||||
min: null,
|
||||
max: null,
|
||||
step: null,
|
||||
unit: null,
|
||||
options: [],
|
||||
allow_blank: true,
|
||||
max_length: null,
|
||||
text_format: 'uuid',
|
||||
},
|
||||
],
|
||||
execution_kind: 'bridge',
|
||||
legacy_compatible: true,
|
||||
input_contract: { context: 'order_line', requires: ['order_line_context'] },
|
||||
output_contract: { context: 'order_line', provides: ['render_template'] },
|
||||
artifact_roles_consumed: ['order_line_context'],
|
||||
artifact_roles_produced: ['render_template'],
|
||||
legacy_source: 'legacy.resolve_template',
|
||||
}
|
||||
|
||||
const notifyDefinition: WorkflowNodeDefinition = {
|
||||
step: 'notify',
|
||||
label: 'Notify',
|
||||
family: 'order_line',
|
||||
module_key: 'notifications.emit',
|
||||
category: 'output',
|
||||
description: 'Emit the workflow result.',
|
||||
node_type: 'outputNode',
|
||||
icon: 'bell',
|
||||
defaults: {},
|
||||
fields: [],
|
||||
execution_kind: 'bridge',
|
||||
legacy_compatible: true,
|
||||
input_contract: { context: 'order_line', requires: ['workflow_result'] },
|
||||
output_contract: { context: 'order_line', provides: ['notification_event'] },
|
||||
artifact_roles_consumed: ['workflow_result'],
|
||||
artifact_roles_produced: ['notification_event'],
|
||||
legacy_source: 'legacy.notify',
|
||||
}
|
||||
|
||||
function createRenderTemplate(overrides: Partial<RenderTemplate> = {}): RenderTemplate {
|
||||
return {
|
||||
id: '0d87b85f-c454-4d61-a124-d5b59e6a43a2',
|
||||
name: 'Bearing Studio',
|
||||
category_key: 'bearings',
|
||||
output_type_id: null,
|
||||
output_type_name: null,
|
||||
output_type_ids: [],
|
||||
output_type_names: ['Still'],
|
||||
blend_file_path: '/templates/bearing.blend',
|
||||
original_filename: 'bearing.blend',
|
||||
target_collection: 'Product',
|
||||
material_replace_enabled: true,
|
||||
lighting_only: false,
|
||||
shadow_catcher_enabled: false,
|
||||
camera_orbit: true,
|
||||
workflow_input_schema: [],
|
||||
is_active: true,
|
||||
created_at: '2026-04-11T00:00:00Z',
|
||||
updated_at: '2026-04-11T00:00:00Z',
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function renderInspector(
|
||||
params: WorkflowParams,
|
||||
onChange = vi.fn(),
|
||||
options: { stateful?: boolean } = {},
|
||||
) {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
function StatefulInspector() {
|
||||
const [currentParams, setCurrentParams] = useState(params)
|
||||
|
||||
return (
|
||||
<WorkflowNodeInspector
|
||||
params={currentParams}
|
||||
onChange={nextParams => {
|
||||
setCurrentParams(nextParams)
|
||||
onChange(nextParams)
|
||||
}}
|
||||
nodeDefinition={resolveTemplateDefinition}
|
||||
step="resolve_template"
|
||||
nodeDefinitions={[resolveTemplateDefinition]}
|
||||
graphFamily="order_line"
|
||||
onStepChange={vi.fn()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{options.stateful ? (
|
||||
<StatefulInspector />
|
||||
) : (
|
||||
<WorkflowNodeInspector
|
||||
params={params}
|
||||
onChange={onChange}
|
||||
nodeDefinition={resolveTemplateDefinition}
|
||||
step="resolve_template"
|
||||
nodeDefinitions={[resolveTemplateDefinition]}
|
||||
graphFamily="order_line"
|
||||
onStepChange={vi.fn()}
|
||||
/>
|
||||
)}
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
|
||||
return { onChange, queryClient }
|
||||
}
|
||||
|
||||
describe('WorkflowNodeInspector', () => {
|
||||
beforeEach(() => {
|
||||
listRenderTemplates.mockReset()
|
||||
})
|
||||
|
||||
test('renders template-defined workflow input fields for resolve_template nodes', async () => {
|
||||
listRenderTemplates.mockResolvedValue([
|
||||
createRenderTemplate({
|
||||
workflow_input_schema: [
|
||||
{
|
||||
key: 'studio_variant',
|
||||
label: 'Studio Variant',
|
||||
type: 'select',
|
||||
section: 'Template Inputs',
|
||||
description: 'Choose the blend lighting preset.',
|
||||
default: 'default',
|
||||
min: null,
|
||||
max: null,
|
||||
step: null,
|
||||
unit: null,
|
||||
options: [
|
||||
{ value: 'default', label: 'Default' },
|
||||
{ value: 'warm', label: 'Warm' },
|
||||
],
|
||||
allow_blank: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
|
||||
renderInspector({
|
||||
template_id_override: '0d87b85f-c454-4d61-a124-d5b59e6a43a2',
|
||||
template_input__studio_variant: 'warm',
|
||||
})
|
||||
|
||||
expect(await screen.findByLabelText('Template Override')).toHaveValue(
|
||||
'0d87b85f-c454-4d61-a124-d5b59e6a43a2',
|
||||
)
|
||||
expect(await screen.findByLabelText('Studio Variant')).toHaveValue('warm')
|
||||
expect(screen.getByText('Bearing Studio')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('clears dynamic template inputs when template override is removed', async () => {
|
||||
listRenderTemplates.mockResolvedValue([
|
||||
createRenderTemplate({
|
||||
workflow_input_schema: [
|
||||
{
|
||||
key: 'studio_variant',
|
||||
label: 'Studio Variant',
|
||||
type: 'select',
|
||||
section: 'Template Inputs',
|
||||
description: 'Choose the blend lighting preset.',
|
||||
default: 'default',
|
||||
min: null,
|
||||
max: null,
|
||||
step: null,
|
||||
unit: null,
|
||||
options: [
|
||||
{ value: 'default', label: 'Default' },
|
||||
{ value: 'warm', label: 'Warm' },
|
||||
],
|
||||
allow_blank: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
|
||||
const user = userEvent.setup()
|
||||
const { onChange } = renderInspector({
|
||||
template_id_override: '0d87b85f-c454-4d61-a124-d5b59e6a43a2',
|
||||
template_input__studio_variant: 'warm',
|
||||
template_input__camera_profile: 'macro',
|
||||
})
|
||||
|
||||
const templateOverride = await screen.findByLabelText('Template Override')
|
||||
await user.selectOptions(templateOverride, '')
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalledWith({})
|
||||
})
|
||||
})
|
||||
|
||||
test('explains connection-driven nodes when no editor fields are available', async () => {
|
||||
listRenderTemplates.mockResolvedValue([])
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<WorkflowNodeInspector
|
||||
params={{}}
|
||||
onChange={vi.fn()}
|
||||
nodeDefinition={notifyDefinition}
|
||||
step="notify"
|
||||
nodeDefinitions={[notifyDefinition]}
|
||||
graphFamily="order_line"
|
||||
onStepChange={vi.fn()}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('This node has no editor settings.')).toBeInTheDocument()
|
||||
expect(screen.getByText(/each required upstream input gets its own socket/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/0 local variables by design/i)).toBeInTheDocument()
|
||||
expect(screen.getByText('Socket 1')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getAllByText('Any of: Rendered Image / Rendered Frames / Rendered Video / Workflow Result / Blend Asset').length,
|
||||
).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('summarizes wired inputs and inspector variables separately', async () => {
|
||||
listRenderTemplates.mockResolvedValue([
|
||||
createRenderTemplate({
|
||||
workflow_input_schema: [
|
||||
{
|
||||
key: 'studio_variant',
|
||||
label: 'Studio Variant',
|
||||
type: 'select',
|
||||
section: 'Template Inputs',
|
||||
description: 'Choose the blend lighting preset.',
|
||||
default: 'default',
|
||||
min: null,
|
||||
max: null,
|
||||
step: null,
|
||||
unit: null,
|
||||
options: [
|
||||
{ value: 'default', label: 'Default' },
|
||||
{ value: 'warm', label: 'Warm' },
|
||||
],
|
||||
allow_blank: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
|
||||
const user = userEvent.setup()
|
||||
renderInspector({}, vi.fn(), { stateful: true })
|
||||
|
||||
const templateOverride = await screen.findByLabelText('Template Override')
|
||||
await waitFor(() => {
|
||||
expect(within(templateOverride).getByRole('option', { name: /bearing studio/i })).toBeInTheDocument()
|
||||
})
|
||||
await user.selectOptions(templateOverride, '0d87b85f-c454-4d61-a124-d5b59e6a43a2')
|
||||
|
||||
expect(await screen.findByText(/1 canvas socket is required/i)).toBeInTheDocument()
|
||||
expect(screen.getByText('Socket 1')).toBeInTheDocument()
|
||||
expect(await screen.findByText(/2 local variables are edited in the inspector/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Static: Template Override/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Template-driven: Studio Variant/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user