chore: import upstream snapshot with attribution
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
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
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
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('@/blocks', () => ({
|
||||
getBlock: (type: string) => {
|
||||
if (type === 'slack') return { name: 'Slack' }
|
||||
if (type === 'workflow' || type === 'workflow_input') return { name: 'Workflow' }
|
||||
return undefined
|
||||
},
|
||||
}))
|
||||
|
||||
import {
|
||||
getDisplayValue,
|
||||
resolveDropdownLabel,
|
||||
resolveFilterFieldLabel,
|
||||
resolveSkillsLabel,
|
||||
resolveToolsLabel,
|
||||
resolveVariablesLabel,
|
||||
resolveWorkflowMultiSelectLabel,
|
||||
resolveWorkflowSelectionLabel,
|
||||
summarizeNames,
|
||||
} from '@/lib/workflows/subblocks/display'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
|
||||
const workflowSelector = { id: 'workflowId', type: 'workflow-selector' } as SubBlockConfig
|
||||
const workflowMulti = {
|
||||
id: 'workflowIds',
|
||||
type: 'dropdown',
|
||||
multiSelect: true,
|
||||
} as SubBlockConfig
|
||||
const variablesInput = { id: 'variables', type: 'variables-input' } as SubBlockConfig
|
||||
const toolInput = { id: 'tools', type: 'tool-input' } as SubBlockConfig
|
||||
const skillInput = { id: 'skills', type: 'skill-input' } as SubBlockConfig
|
||||
|
||||
describe('summarizeNames', () => {
|
||||
it('formats 0, 1, 2, and 2+N name lists', () => {
|
||||
expect(summarizeNames([])).toBeNull()
|
||||
expect(summarizeNames(['A'])).toBe('A')
|
||||
expect(summarizeNames(['A', 'B'])).toBe('A, B')
|
||||
expect(summarizeNames(['A', 'B', 'C', 'D'])).toBe('A, B +2')
|
||||
})
|
||||
})
|
||||
|
||||
describe('workflow selection labels', () => {
|
||||
const lookup = { workflowMap: { 'wf-1': { name: 'Billing' } }, ready: true }
|
||||
|
||||
it('resolves a single workflow selection to its name', () => {
|
||||
expect(resolveWorkflowSelectionLabel(workflowSelector, 'wf-1', lookup)).toBe('Billing')
|
||||
})
|
||||
|
||||
it('labels missing workflows as deleted only after the lookup is ready', () => {
|
||||
expect(resolveWorkflowSelectionLabel(workflowSelector, 'wf-gone', lookup)).toBe(
|
||||
'Deleted Workflow'
|
||||
)
|
||||
expect(
|
||||
resolveWorkflowSelectionLabel(workflowSelector, 'wf-gone', { ...lookup, ready: false })
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('summarizes multi-select workflow ids with the deleted fallback', () => {
|
||||
expect(resolveWorkflowMultiSelectLabel(workflowMulti, ['wf-1', 'wf-gone'], lookup)).toBe(
|
||||
'Billing, Deleted Workflow'
|
||||
)
|
||||
expect(
|
||||
resolveWorkflowMultiSelectLabel(workflowMulti, ['wf-1'], { ...lookup, ready: false })
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('matches multi-select subblocks by canonicalParamId as well as id', () => {
|
||||
const canonical = {
|
||||
id: 'workflowSelector',
|
||||
type: 'dropdown',
|
||||
multiSelect: true,
|
||||
canonicalParamId: 'workflowIds',
|
||||
} as SubBlockConfig
|
||||
expect(resolveWorkflowMultiSelectLabel(canonical, ['wf-1'], lookup)).toBe('Billing')
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveVariablesLabel', () => {
|
||||
it('resolves variable ids to live names and falls back to stored names', () => {
|
||||
const variables = [{ id: 'var-1', name: 'apiKey' }]
|
||||
expect(
|
||||
resolveVariablesLabel(
|
||||
variablesInput,
|
||||
[
|
||||
{ variableId: 'var-1', value: 1 },
|
||||
{ variableName: 'region', value: 2 },
|
||||
],
|
||||
variables
|
||||
)
|
||||
).toBe('apiKey, region')
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveToolsLabel', () => {
|
||||
it('resolves titles, custom tools by id, schema names, and registry blocks', () => {
|
||||
const customTools = [{ id: 'ct-1', title: 'My Tool' }]
|
||||
expect(
|
||||
resolveToolsLabel(
|
||||
toolInput,
|
||||
[
|
||||
{ title: 'Explicit' },
|
||||
{ type: 'custom-tool', customToolId: 'ct-1' },
|
||||
{ schema: { function: { name: 'inline_fn' } } },
|
||||
{ type: 'slack' },
|
||||
],
|
||||
customTools
|
||||
)
|
||||
).toBe('Explicit, My Tool +2')
|
||||
})
|
||||
|
||||
it('skips unresolvable entries instead of inventing labels', () => {
|
||||
expect(resolveToolsLabel(toolInput, [{ type: 'custom-tool', customToolId: 'gone' }], [])).toBe(
|
||||
null
|
||||
)
|
||||
})
|
||||
|
||||
it('ignores the stored title on registry-backed tools so state edits cannot rename them', () => {
|
||||
const slackName = resolveToolsLabel(toolInput, [{ type: 'slack' }], [])
|
||||
expect(slackName).not.toBe(null)
|
||||
expect(resolveToolsLabel(toolInput, [{ type: 'slack', title: 'Renamed By Copilot' }], [])).toBe(
|
||||
slackName
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to the stored title, then the raw type id, for unresolvable block types', () => {
|
||||
expect(
|
||||
resolveToolsLabel(toolInput, [{ type: 'custom_block_gone', title: 'Invoice Parser' }], [])
|
||||
).toBe('Invoice Parser')
|
||||
expect(resolveToolsLabel(toolInput, [{ type: 'custom_block_gone' }], [])).toBe(
|
||||
'custom_block_gone'
|
||||
)
|
||||
})
|
||||
|
||||
it('renders the static label for workflow-as-tool entries regardless of stored title', () => {
|
||||
expect(
|
||||
resolveToolsLabel(toolInput, [{ type: 'workflow', title: 'Renamed By Copilot' }], [])
|
||||
).toBe('Workflow')
|
||||
expect(resolveToolsLabel(toolInput, [{ type: 'workflow_input' }], [])).toBe('Workflow')
|
||||
})
|
||||
|
||||
it('prefers the live MCP tool name over the stored title', () => {
|
||||
expect(
|
||||
resolveToolsLabel(
|
||||
toolInput,
|
||||
[{ type: 'mcp', toolId: 'mcp-1', title: 'Renamed By Copilot' }],
|
||||
[],
|
||||
new Map([['mcp-1', 'Live MCP Name']])
|
||||
)
|
||||
).toBe('Live MCP Name')
|
||||
expect(
|
||||
resolveToolsLabel(toolInput, [{ type: 'mcp', toolId: 'mcp-1', title: 'Snapshot' }], [])
|
||||
).toBe('Snapshot')
|
||||
})
|
||||
|
||||
it('prefers the custom tool record over the stored title', () => {
|
||||
expect(
|
||||
resolveToolsLabel(
|
||||
toolInput,
|
||||
[{ type: 'custom-tool', customToolId: 'ct-1', title: 'Renamed By Copilot' }],
|
||||
[{ id: 'ct-1', title: 'My Tool' }]
|
||||
)
|
||||
).toBe('My Tool')
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveSkillsLabel', () => {
|
||||
it('prefers live skill names and falls back to the stored name', () => {
|
||||
const skills = [{ id: 'sk-1', name: 'Research' }]
|
||||
expect(
|
||||
resolveSkillsLabel(
|
||||
skillInput,
|
||||
[{ skillId: 'sk-1' }, { skillId: 'sk-deleted', name: 'Old Name' }],
|
||||
skills
|
||||
)
|
||||
).toBe('Research, Old Name')
|
||||
})
|
||||
|
||||
it('never renders raw skill ids', () => {
|
||||
expect(resolveSkillsLabel(skillInput, [{ skillId: 'sk-unknown' }], [])).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveDropdownLabel', () => {
|
||||
const dropdown = {
|
||||
id: 'mode',
|
||||
type: 'dropdown',
|
||||
options: [{ id: 'opt-1', label: 'Option One' }, 'literal'],
|
||||
} as SubBlockConfig
|
||||
|
||||
it('resolves option ids and string options to labels', () => {
|
||||
expect(resolveDropdownLabel(dropdown, 'opt-1')).toBe('Option One')
|
||||
expect(resolveDropdownLabel(dropdown, 'literal')).toBe('literal')
|
||||
expect(resolveDropdownLabel(dropdown, 'missing')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveFilterFieldLabel', () => {
|
||||
const filterField = { id: 'filter', type: 'short-input' } as SubBlockConfig
|
||||
|
||||
it('renders compact JSON for filter fields and truncates long values', () => {
|
||||
expect(resolveFilterFieldLabel(filterField, '{"a":1}')).toBe('{"a":1}')
|
||||
const long = JSON.stringify({ column: 'status', operator: 'contains', value: 'running' })
|
||||
expect(resolveFilterFieldLabel(filterField, long)).toBe(`${long.slice(0, 32)}...`)
|
||||
})
|
||||
|
||||
it('returns null for non-filter subblocks and non-JSON values', () => {
|
||||
expect(
|
||||
resolveFilterFieldLabel({ id: 'other', type: 'short-input' } as SubBlockConfig, '{"a":1}')
|
||||
).toBeNull()
|
||||
expect(resolveFilterFieldLabel(filterField, 'plain text')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getDisplayValue', () => {
|
||||
it('handles empty, scalar, and object values', () => {
|
||||
expect(getDisplayValue(null)).toBe('-')
|
||||
expect(getDisplayValue('hello')).toBe('hello')
|
||||
expect(getDisplayValue({ a: 1 })).toBe('a: 1')
|
||||
})
|
||||
|
||||
it('summarizes name-bearing arrays', () => {
|
||||
expect(
|
||||
getDisplayValue([
|
||||
{ variableName: 'one', variableId: 'v1', value: 1 },
|
||||
{ variableName: 'two', variableId: 'v2', value: 2 },
|
||||
{ variableName: 'three', variableId: 'v3', value: 3 },
|
||||
])
|
||||
).toBe('one, two +1')
|
||||
expect(getDisplayValue(['a', 'b'])).toBe('a, b')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user