Files
simstudioai--sim/apps/sim/blocks/custom/build-config.test.ts
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

131 lines
4.8 KiB
TypeScript

/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import type { WorkflowInputField } from '@/lib/workflows/input-format'
import {
buildCustomBlockConfig,
CUSTOM_BLOCK_TILE_COLOR,
type CustomBlockRow,
isCustomBlockType,
} from '@/blocks/custom/build-config'
import type { BlockIcon } from '@/blocks/types'
const icon: BlockIcon = () => null as never
const row: CustomBlockRow = {
type: 'custom_block_abc123',
name: 'Invoice Parser',
description: 'Extracts fields from an invoice',
workflowId: 'wf-1',
}
function findSub(config: ReturnType<typeof buildCustomBlockConfig>, id: string) {
return config.subBlocks.find((s) => s.id === id)
}
describe('isCustomBlockType', () => {
it('matches only the custom_block_ prefix', () => {
expect(isCustomBlockType('custom_block_abc')).toBe(true)
expect(isCustomBlockType('agent')).toBe(false)
expect(isCustomBlockType(undefined)).toBe(false)
expect(isCustomBlockType(null)).toBe(false)
})
})
describe('buildCustomBlockConfig', () => {
const fields: WorkflowInputField[] = [
{ name: 'title', type: 'string' },
{ name: 'count', type: 'number' },
{ name: 'flag', type: 'boolean' },
{ name: 'payload', type: 'object' },
{ name: 'items', type: 'array' },
{ name: 'docs', type: 'file[]' },
]
it('carries the row identity and always wires the workflow_executor tool', () => {
const config = buildCustomBlockConfig(row, fields, { icon })
expect(config.type).toBe('custom_block_abc123')
expect(config.name).toBe('Invoice Parser')
expect(config.category).toBe('tools')
expect(config.bgColor).toBe(CUSTOM_BLOCK_TILE_COLOR)
expect(config.hideFromToolbar).toBeUndefined()
expect(config.tools.access).toEqual(['workflow_executor'])
expect(config.tools.config?.tool({})).toBe('workflow_executor')
})
it('hides a disabled block from the toolbar while keeping it resolvable', () => {
expect(buildCustomBlockConfig(row, fields, { icon }).hideFromToolbar).toBeUndefined()
expect(
buildCustomBlockConfig(row, fields, { icon, hideFromToolbar: true }).hideFromToolbar
).toBe(true)
})
it('bakes the bound workflowId as a hidden sub-block', () => {
const config = buildCustomBlockConfig(row, fields, { icon })
const wf = findSub(config, 'workflowId')
expect(wf?.hidden).toBe(true)
expect(wf?.value?.({})).toBe('wf-1')
})
it('maps each input field type to the right sub-block', () => {
const config = buildCustomBlockConfig(row, fields, { icon })
expect(findSub(config, 'title')?.type).toBe('short-input')
expect(findSub(config, 'count')?.type).toBe('short-input')
expect(findSub(config, 'flag')?.type).toBe('switch')
expect(findSub(config, 'payload')?.type).toBe('code')
expect(findSub(config, 'payload')?.language).toBe('json')
expect(findSub(config, 'items')?.type).toBe('code')
expect(findSub(config, 'docs')?.type).toBe('file-upload')
expect(findSub(config, 'docs')?.multiple).toBe(true)
})
it('exposes the full result and hides plumbing when no outputs are curated', () => {
const config = buildCustomBlockConfig(row, fields, { icon })
expect(Object.keys(config.outputs).sort()).toEqual(['error', 'result', 'success'])
expect(config.outputs.childWorkflowId).toBeUndefined()
expect(config.outputs.childTraceSpans).toBeUndefined()
})
it('exposes only curated outputs as named fields', () => {
const config = buildCustomBlockConfig(
{ ...row, exposedOutputs: [{ blockId: 'b1', path: 'content', name: 'email' }] },
fields,
{ icon }
)
expect(config.outputs.email).toEqual({ type: 'json', description: 'Output: content' })
expect(config.outputs.result).toBeUndefined()
expect(config.outputs.success).toBeDefined()
expect(config.outputs.childWorkflowId).toBeUndefined()
})
it('anchors the sub-block on the stable field id, showing the name as title', () => {
const config = buildCustomBlockConfig(row, [{ id: 'fld-1', name: 'title', type: 'string' }], {
icon,
})
const sub = findSub(config, 'fld-1')
expect(sub).toBeDefined()
expect(sub?.title).toBe('title')
expect(findSub(config, 'title')).toBeUndefined()
})
it('falls back to the field name as id when a field has no stable id', () => {
const config = buildCustomBlockConfig(row, [{ name: 'legacy', type: 'string' }], { icon })
expect(findSub(config, 'legacy')?.title).toBe('legacy')
})
it('assembles inputMapping from non-reserved, non-empty params', () => {
const config = buildCustomBlockConfig(row, fields, { icon })
const mappingFn = findSub(config, 'inputMapping')?.value
const json = mappingFn?.({
workflowId: 'wf-1',
inputMapping: 'ignored',
triggerMode: true,
title: 'Acme',
count: 3,
empty: '',
})
expect(JSON.parse(json as string)).toEqual({ title: 'Acme', count: 3 })
})
})