Skip to content

Commit f3c4d67

Browse files
fix(pickers): restore modal interactions and surface workflows
1 parent ce1fe94 commit f3c4d67

3 files changed

Lines changed: 152 additions & 35 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { act, useState } from 'react'
5+
import { createRoot, type Root } from 'react-dom/client'
6+
import { afterEach, describe, expect, it, vi } from 'vitest'
7+
8+
vi.mock('zustand/react/shallow', () => ({ useShallow: (selector: unknown) => selector }))
9+
10+
vi.mock('@/blocks/block-tile', () => ({
11+
BlockTile: ({ blockType }: { blockType: string }) => <span data-block-type={blockType} />,
12+
}))
13+
14+
vi.mock('@/hooks/queries/workflows', () => ({ useWorkflowStates: () => new Map() }))
15+
16+
vi.mock('@/stores/workflow-diff/store', () => ({
17+
useWorkflowDiffStore: (selector: (state: object) => unknown) =>
18+
selector({
19+
isShowingDiff: false,
20+
isDiffReady: false,
21+
hasActiveDiff: false,
22+
baselineWorkflow: null,
23+
}),
24+
}))
25+
26+
vi.mock('@/stores/workflows/subblock/store', () => ({
27+
useSubBlockStore: (selector: (state: object) => unknown) =>
28+
selector({ workflowValues: { root: {} } }),
29+
}))
30+
31+
vi.mock('@/stores/workflows/workflow/store', () => ({
32+
useWorkflowStore: (selector: (state: object) => unknown) => selector({ blocks: {}, edges: [] }),
33+
}))
34+
35+
vi.mock('@/lib/workflows/streaming/nested-output-options', () => {
36+
const rootOutput = {
37+
id: 'summary_content',
38+
label: 'Summarizer.content',
39+
blockId: 'summary',
40+
blockName: 'Summarizer',
41+
blockType: 'agent',
42+
groupKey: 'summary',
43+
groupLabel: 'Summarizer',
44+
path: 'content',
45+
menuPath: [],
46+
}
47+
48+
return {
49+
collectReferencedWorkflowIds: () => [],
50+
buildWorkflowOutputOptions: () => [rootOutput],
51+
buildWorkflowOutputMenu: () => [
52+
{
53+
blockId: 'summary',
54+
blockName: 'Summarizer',
55+
blockType: 'agent',
56+
outputs: [rootOutput],
57+
children: [],
58+
},
59+
],
60+
}
61+
})
62+
63+
import { OutputSelect } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select'
64+
65+
let root: Root | null = null
66+
let container: HTMLDivElement | null = null
67+
68+
function renderPicker() {
69+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
70+
container = document.createElement('div')
71+
document.body.appendChild(container)
72+
root = createRoot(container)
73+
74+
function Picker() {
75+
const [selectedOutputs, setSelectedOutputs] = useState<string[]>([])
76+
return (
77+
<OutputSelect
78+
workflowId='root'
79+
selectedOutputs={selectedOutputs}
80+
onOutputSelect={setSelectedOutputs}
81+
size='md'
82+
/>
83+
)
84+
}
85+
86+
act(() => root?.render(<Picker />))
87+
}
88+
89+
afterEach(() => {
90+
if (root) act(() => root?.unmount())
91+
container?.remove()
92+
root = null
93+
container = null
94+
})
95+
96+
describe('OutputSelect DOM interaction', () => {
97+
it('selects an output through the real medium combobox', () => {
98+
renderPicker()
99+
100+
const trigger = document.querySelector<HTMLElement>('[role="combobox"]')
101+
if (!trigger) throw new Error('Output picker trigger did not render')
102+
act(() => trigger.click())
103+
104+
const option = [...document.querySelectorAll<HTMLElement>('[role="option"]')].find(
105+
(candidate) => candidate.textContent === 'content'
106+
)
107+
if (!option) throw new Error('Output option did not render')
108+
const floatingSurface = option.closest<HTMLElement>('[data-native-surface-overlay]')
109+
if (!floatingSurface) throw new Error('Output picker floating surface did not render')
110+
111+
expect(floatingSurface.className).toContain('pointer-events-auto')
112+
113+
act(() => option.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })))
114+
115+
expect(trigger.textContent).toContain('1 output')
116+
expect(document.body.textContent).toContain('Selected')
117+
})
118+
})

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx‎

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,39 @@ export const ToolInput = memo(function ToolInput({
12571257
groups.push({ items: actionItems })
12581258
}
12591259

1260+
if (availableWorkflows.length > 0) {
1261+
groups.push({
1262+
section: 'Workflows',
1263+
items: availableWorkflows.map((workflow) => {
1264+
const alreadySelected = isWorkflowAlreadySelected(selectedTools, workflow.id)
1265+
return {
1266+
label: workflow.name,
1267+
value: `workflow-${workflow.id}`,
1268+
iconElement: createToolIcon('#6366F1', WorkflowIcon),
1269+
onSelect: () => {
1270+
if (alreadySelected) return
1271+
const newTool: StoredTool = {
1272+
type: 'workflow_input',
1273+
title: 'Workflow',
1274+
toolId: 'workflow_executor',
1275+
params: {
1276+
workflowId: workflow.id,
1277+
},
1278+
isExpanded: true,
1279+
usageControl: 'auto',
1280+
}
1281+
setStoreValue([
1282+
...selectedTools.map((tool) => ({ ...tool, isExpanded: false })),
1283+
newTool,
1284+
])
1285+
setOpen(false)
1286+
},
1287+
disabled: isPreview || disabled || alreadySelected,
1288+
}
1289+
}),
1290+
})
1291+
}
1292+
12601293
if (!permissionConfig.disableCustomTools && !customUnsupported && customTools.length > 0) {
12611294
groups.push({
12621295
section: 'Custom Tools',
@@ -1355,40 +1388,6 @@ export const ToolInput = memo(function ToolInput({
13551388
})
13561389
}
13571390

1358-
// Workflows section - shows available workflows that can be executed as tools
1359-
if (availableWorkflows.length > 0) {
1360-
groups.push({
1361-
section: 'Workflows',
1362-
items: availableWorkflows.map((workflow) => {
1363-
const alreadySelected = isWorkflowAlreadySelected(selectedTools, workflow.id)
1364-
return {
1365-
label: workflow.name,
1366-
value: `workflow-${workflow.id}`,
1367-
iconElement: createToolIcon('#6366F1', WorkflowIcon),
1368-
onSelect: () => {
1369-
if (alreadySelected) return
1370-
const newTool: StoredTool = {
1371-
type: 'workflow_input',
1372-
title: 'Workflow',
1373-
toolId: 'workflow_executor',
1374-
params: {
1375-
workflowId: workflow.id,
1376-
},
1377-
isExpanded: true,
1378-
usageControl: 'auto',
1379-
}
1380-
setStoreValue([
1381-
...selectedTools.map((tool) => ({ ...tool, isExpanded: false })),
1382-
newTool,
1383-
])
1384-
setOpen(false)
1385-
},
1386-
disabled: isPreview || disabled || alreadySelected,
1387-
}
1388-
}),
1389-
})
1390-
}
1391-
13921391
if (!permissionConfig.disableMcpTools && supportsAdvancedMcpServer) {
13931392
groups.push({
13941393
section: 'Advanced',

‎packages/emcn/src/components/popover/popover.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ const PopoverContent = React.forwardRef<
601601
{...restProps}
602602
data-native-surface-overlay=''
603603
className={cn(
604-
'z-[var(--z-popover)] flex flex-col outline-none',
604+
'pointer-events-auto z-[var(--z-popover)] flex flex-col outline-none',
605605
showArrow ? 'overflow-visible' : 'overflow-auto',
606606
STYLES.colorScheme[colorScheme].content,
607607
STYLES.content,

0 commit comments

Comments
 (0)