fix(web): sync workflow node catalog after availability changes (#44769)

This commit is contained in:
Stephen Zhou authored and GitHub committed 2026-10-26 03:30:06 +00:00
1 parent a5cf299d1b
commit e758d8c30c
2 files changed
+87 -4

No files matched your search

@@ -1,13 +1,43 @@
import type { AvailableNodesMetaData } from '../store'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { BlockClassification } from '../../block-selector/types'
import { BlockEnum } from '../../types'
import { HooksStoreContextProvider } from '../provider'
import { useHooksStore } from '../store'
vi.mock('reactflow', () => ({
useStore: (selector: (state: { d3Selection: null; d3Zoom: null }) => unknown) =>
selector({ d3Selection: null, d3Zoom: null }),
const canvas = vi.hoisted(() => ({
d3Selection: null as object | null,
d3Zoom: null as object | null,
}))
vi.mock('reactflow', () => ({
useStore: (selector: (state: typeof canvas) => unknown) => selector(canvas),
}))
const createCatalog = (...types: BlockEnum[]): AvailableNodesMetaData => ({
nodes: types.map((type) => ({
metaData: {
classification: BlockClassification.Default,
sort: 0,
type,
title: type,
author: 'Dify',
},
defaultValue: {},
checkValid: () => ({ isValid: true }),
})),
})
const AvailableNodes = () => {
const catalog = useHooksStore((state) => state.availableNodesMetaData)
return (
<output aria-label="Available nodes">
{catalog?.nodes.map((node) => node.metaData.type).join(', ')}
</output>
)
}
const ExportButton = () => {
const isExporting = useHooksStore((state) => state.isExporting)
const handleExportDSL = useHooksStore((state) => state.handleExportDSL)
@@ -19,6 +49,49 @@ const ExportButton = () => {
}
describe('hooks-store provider', () => {
beforeEach(() => {
canvas.d3Selection = null
canvas.d3Zoom = null
})
it.each(['before', 'after'] as const)(
'propagates node catalog changes %s canvas initialization',
(timing) => {
if (timing === 'after') {
canvas.d3Selection = {}
canvas.d3Zoom = {}
}
const initialCatalog = createCatalog(BlockEnum.KnowledgeRetrieval)
const enabledCatalog = createCatalog(
BlockEnum.KnowledgeRetrieval,
BlockEnum.KnowledgeRetrievalV2,
)
const { rerender } = render(
<HooksStoreContextProvider availableNodesMetaData={initialCatalog}>
<AvailableNodes />
</HooksStoreContextProvider>,
)
const availableNodes = screen.getByRole('status', { name: 'Available nodes' })
expect(availableNodes).toHaveTextContent(BlockEnum.KnowledgeRetrieval)
expect(availableNodes).not.toHaveTextContent(BlockEnum.KnowledgeRetrievalV2)
rerender(
<HooksStoreContextProvider availableNodesMetaData={enabledCatalog}>
<AvailableNodes />
</HooksStoreContextProvider>,
)
expect(availableNodes).toHaveTextContent(BlockEnum.KnowledgeRetrievalV2)
rerender(
<HooksStoreContextProvider availableNodesMetaData={initialCatalog}>
<AvailableNodes />
</HooksStoreContextProvider>,
)
expect(availableNodes).toHaveTextContent(BlockEnum.KnowledgeRetrieval)
expect(availableNodes).not.toHaveTextContent(BlockEnum.KnowledgeRetrievalV2)
},
)
it('propagates export pending state and the latest handler independently of canvas initialization', async () => {
const user = userEvent.setup()
const firstExport = vi.fn().mockResolvedValue(true)
@@ -16,6 +16,7 @@ export const HooksStoreContextProvider = ({
const d3Selection = useStore((s) => s.d3Selection)
const d3Zoom = useStore((s) => s.d3Zoom)
const {
availableNodesMetaData,
fileSettings,
accessControl,
exportCheck,
@@ -29,13 +30,22 @@ export const HooksStoreContextProvider = ({
useEffect(() => {
store.getState().refreshAll({
...(availableNodesMetaData && { availableNodesMetaData }),
...(accessControl && { accessControl }),
...(exportCheck && { exportCheck }),
...(handleExportDSL && { handleExportDSL }),
isExporting,
fileSettings,
})
}, [fileSettings, accessControl, exportCheck, handleExportDSL, isExporting, store])
}, [
availableNodesMetaData,
fileSettings,
accessControl,
exportCheck,
handleExportDSL,
isExporting,
store,
])
return <HooksStoreContext.Provider value={store}>{children}</HooksStoreContext.Provider>
}