diff --git a/web/app/components/app/workflow-log/__tests__/detail.spec.tsx b/web/app/components/app/workflow-log/__tests__/detail.spec.tsx
index e3b024689e8..d275c885b53 100644
--- a/web/app/components/app/workflow-log/__tests__/detail.spec.tsx
+++ b/web/app/components/app/workflow-log/__tests__/detail.spec.tsx
@@ -1,4 +1,3 @@
-import type { AppDetailWithSite } from '@dify/contracts/api/console/apps/types.gen'
/**
* DetailPanel Component Tests
*
@@ -10,8 +9,6 @@ import type { AppDetailWithSite } from '@dify/contracts/api/console/apps/types.g
*/
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
-import { useStore as useAppStore } from '@/app/components/app/store'
-import { createAppDetailFixture, createAppSiteFixture } from '@/test/fixtures/app'
import DetailPanel from '../detail'
// ============================================================================
@@ -54,29 +51,13 @@ vi.mock('ahooks', () => ({
},
}))
-// ============================================================================
-// Test Data Factories
-// ============================================================================
-
-const createMockApp = (overrides: Partial
= {}): AppDetailWithSite =>
- createAppDetailFixture({
- id: 'test-app-id',
- name: 'Test App',
- mode: 'workflow',
- site: createAppSiteFixture({ access_token: 'token', app_base_url: 'https://example.com' }),
- ...overrides,
- })
-
-// ============================================================================
-// Tests
-// ============================================================================
-
describe('DetailPanel', () => {
const defaultOnClose = vi.fn()
+ let appId = 'test-app-id'
beforeEach(() => {
vi.clearAllMocks()
- useAppStore.setState({ appDetail: createMockApp() })
+ appId = 'test-app-id'
})
// --------------------------------------------------------------------------
@@ -84,21 +65,21 @@ describe('DetailPanel', () => {
// --------------------------------------------------------------------------
describe('Rendering', () => {
it('should render workflow title', () => {
- render()
+ render()
expect(screen.getByText('appLog.runDetail.workflowTitle')).toBeInTheDocument()
})
it('should render close button', () => {
- render()
+ render()
expect(screen.getByRole('button', { name: 'common.operation.close' })).toBeInTheDocument()
})
it('should render Run component with correct URLs', () => {
- useAppStore.setState({ appDetail: createMockApp({ id: 'app-456' }) })
+ appId = 'app-456'
- render()
+ render()
expect(screen.getByTestId('workflow-run')).toBeInTheDocument()
expect(screen.getByTestId('run-detail-url')).toHaveTextContent(
@@ -110,7 +91,7 @@ describe('DetailPanel', () => {
})
it('should render WorkflowContextProvider wrapper', () => {
- render()
+ render()
expect(screen.getByTestId('workflow-context-provider')).toBeInTheDocument()
})
@@ -121,7 +102,7 @@ describe('DetailPanel', () => {
// --------------------------------------------------------------------------
describe('Props', () => {
it('should not render replay button when canReplay is false (default)', () => {
- render()
+ render()
expect(
screen.queryByRole('button', { name: 'appLog.runDetail.testWithParams' }),
@@ -129,7 +110,9 @@ describe('DetailPanel', () => {
})
it('should render replay button when canReplay is true', () => {
- render()
+ render(
+ ,
+ )
expect(
screen.getByRole('button', { name: 'appLog.runDetail.testWithParams' }),
@@ -137,7 +120,7 @@ describe('DetailPanel', () => {
})
it('should use empty URL when runID is empty', () => {
- render()
+ render()
expect(screen.getByTestId('run-detail-url')).toHaveTextContent('')
expect(screen.getByTestId('tracing-list-url')).toHaveTextContent('')
@@ -152,7 +135,7 @@ describe('DetailPanel', () => {
const user = userEvent.setup()
const onClose = vi.fn()
- render()
+ render()
const closeButton = screen.getByRole('button', { name: 'common.operation.close' })
@@ -163,9 +146,16 @@ describe('DetailPanel', () => {
it('should navigate to workflow page with replayRunId when replay button is clicked', async () => {
const user = userEvent.setup()
- useAppStore.setState({ appDetail: createMockApp({ id: 'app-replay-test' }) })
+ appId = 'app-replay-test'
- render()
+ render(
+ ,
+ )
const replayButton = screen.getByRole('button', { name: 'appLog.runDetail.testWithParams' })
await user.click(replayButton)
@@ -174,18 +164,6 @@ describe('DetailPanel', () => {
'/app/app-replay-test/workflow?replayRunId=run-to-replay',
)
})
-
- it('should not navigate when replay clicked but appDetail is missing', async () => {
- const user = userEvent.setup()
- useAppStore.setState({ appDetail: undefined })
-
- render()
-
- const replayButton = screen.getByRole('button', { name: 'appLog.runDetail.testWithParams' })
- await user.click(replayButton)
-
- expect(mockRouterPush).not.toHaveBeenCalled()
- })
})
// --------------------------------------------------------------------------
@@ -193,9 +171,9 @@ describe('DetailPanel', () => {
// --------------------------------------------------------------------------
describe('URL Generation', () => {
it('should generate correct run detail URL', () => {
- useAppStore.setState({ appDetail: createMockApp({ id: 'my-app' }) })
+ appId = 'my-app'
- render()
+ render()
expect(screen.getByTestId('run-detail-url')).toHaveTextContent(
'/apps/my-app/workflow-runs/my-run',
@@ -203,9 +181,9 @@ describe('DetailPanel', () => {
})
it('should generate correct tracing list URL', () => {
- useAppStore.setState({ appDetail: createMockApp({ id: 'my-app' }) })
+ appId = 'my-app'
- render()
+ render()
expect(screen.getByTestId('tracing-list-url')).toHaveTextContent(
'/apps/my-app/workflow-runs/my-run/node-executions',
@@ -213,9 +191,9 @@ describe('DetailPanel', () => {
})
it('should handle special characters in runID', () => {
- useAppStore.setState({ appDetail: createMockApp({ id: 'app-id' }) })
+ appId = 'app-id'
- render()
+ render()
expect(screen.getByTestId('run-detail-url')).toHaveTextContent(
'/apps/app-id/workflow-runs/run-with-special-123',
@@ -223,36 +201,12 @@ describe('DetailPanel', () => {
})
})
- // --------------------------------------------------------------------------
- // Store Integration Tests
- // --------------------------------------------------------------------------
- describe('Store Integration', () => {
- it('should read appDetail from store', () => {
- useAppStore.setState({ appDetail: createMockApp({ id: 'store-app-id' }) })
-
- render()
-
- expect(screen.getByTestId('run-detail-url')).toHaveTextContent(
- '/apps/store-app-id/workflow-runs/run-123',
- )
- })
-
- it('should handle undefined appDetail from store gracefully', () => {
- useAppStore.setState({ appDetail: undefined })
-
- render()
-
- // Run component should still render but with undefined in URL
- expect(screen.getByTestId('workflow-run')).toBeInTheDocument()
- })
- })
-
// --------------------------------------------------------------------------
// Edge Cases (REQUIRED)
// --------------------------------------------------------------------------
describe('Edge Cases', () => {
it('should handle empty runID', () => {
- render()
+ render()
expect(screen.getByTestId('run-detail-url')).toHaveTextContent('')
expect(screen.getByTestId('tracing-list-url')).toHaveTextContent('')
@@ -260,9 +214,9 @@ describe('DetailPanel', () => {
it('should handle very long runID', () => {
const longRunId = 'a'.repeat(100)
- useAppStore.setState({ appDetail: createMockApp({ id: 'app-id' }) })
+ appId = 'app-id'
- render()
+ render()
expect(screen.getByTestId('run-detail-url')).toHaveTextContent(
`/apps/app-id/workflow-runs/${longRunId}`,
@@ -270,14 +224,18 @@ describe('DetailPanel', () => {
})
it('should render replay button with correct aria-label', () => {
- render()
+ render(
+ ,
+ )
const replayButton = screen.getByRole('button', { name: 'appLog.runDetail.testWithParams' })
expect(replayButton).toHaveAttribute('aria-label', 'appLog.runDetail.testWithParams')
})
it('should maintain proper component structure', () => {
- const { container } = render()
+ const { container } = render(
+ ,
+ )
// Check for main container with flex layout
const mainContainer = container.querySelector('.flex.grow.flex-col')
@@ -294,7 +252,9 @@ describe('DetailPanel', () => {
// --------------------------------------------------------------------------
describe('Tooltip', () => {
it('should have tooltip on replay button', () => {
- render()
+ render(
+ ,
+ )
// The replay button should be wrapped in TooltipPlus
const replayButton = screen.getByRole('button', { name: 'appLog.runDetail.testWithParams' })
diff --git a/web/app/components/app/workflow-log/__tests__/list.spec.tsx b/web/app/components/app/workflow-log/__tests__/list.spec.tsx
index 224b088f90a..3a672ae8e9e 100644
--- a/web/app/components/app/workflow-log/__tests__/list.spec.tsx
+++ b/web/app/components/app/workflow-log/__tests__/list.spec.tsx
@@ -13,7 +13,6 @@ import type { WorkflowAppLogDetail, WorkflowLogsResponse, WorkflowRunDetail } fr
import type { AppModeEnum } from '@/types/app'
import { render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
-import { useStore as useAppStore } from '@/app/components/app/store'
import { APP_PAGE_LIMIT } from '@/config'
import { WorkflowRunTriggeredFrom } from '@/models/log'
import { createAppDetailFixture, createAppSiteFixture } from '@/test/fixtures/app'
@@ -150,7 +149,6 @@ describe('WorkflowAppLogList', () => {
beforeEach(() => {
vi.clearAllMocks()
- useAppStore.setState({ appDetail: createMockApp() })
})
// --------------------------------------------------------------------------
@@ -447,7 +445,7 @@ describe('WorkflowAppLogList', () => {
describe('Drawer', () => {
it('should open drawer when clicking on a log row', async () => {
const user = userEvent.setup()
- useAppStore.setState({ appDetail: createMockApp({ id: 'app-123' }) })
+
const logs = createMockLogsResponse([
createMockWorkflowLog({
id: 'log-1',
@@ -473,7 +471,7 @@ describe('WorkflowAppLogList', () => {
const user = userEvent.setup()
const appDetail = createMockApp()
const onRefresh = vi.fn()
- useAppStore.setState({ appDetail })
+
const logs = createMockLogsResponse([
createMockWorkflowLog({ id: 'log-1', created_at: 100 }),
createMockWorkflowLog({ id: 'log-2', created_at: 200 }),
@@ -508,7 +506,7 @@ describe('WorkflowAppLogList', () => {
it('should close drawer and call onRefresh when closing', async () => {
const user = userEvent.setup()
const onRefresh = vi.fn()
- useAppStore.setState({ appDetail: createMockApp() })
+
const logs = createMockLogsResponse([createMockWorkflowLog()])
render()
@@ -588,7 +586,7 @@ describe('WorkflowAppLogList', () => {
describe('Replay Functionality', () => {
it('should allow replay when triggered from app-run', async () => {
const user = userEvent.setup()
- useAppStore.setState({ appDetail: createMockApp({ id: 'app-replay' }) })
+
const logs = createMockLogsResponse([
createMockWorkflowLog({
workflow_run: createMockWorkflowRun({
@@ -612,13 +610,13 @@ describe('WorkflowAppLogList', () => {
await user.click(replayButton)
expect(mockRouterPush).toHaveBeenCalledWith(
- '/app/app-replay/workflow?replayRunId=run-to-replay',
+ '/app/test-app-id/workflow?replayRunId=run-to-replay',
)
})
it('should allow replay when triggered from debugging', async () => {
const user = userEvent.setup()
- useAppStore.setState({ appDetail: createMockApp({ id: 'app-debug' }) })
+
const logs = createMockLogsResponse([
createMockWorkflowLog({
workflow_run: createMockWorkflowRun({
@@ -644,7 +642,7 @@ describe('WorkflowAppLogList', () => {
it('should not show replay for webhook triggers', async () => {
const user = userEvent.setup()
- useAppStore.setState({ appDetail: createMockApp({ id: 'app-webhook' }) })
+
const logs = createMockLogsResponse([
createMockWorkflowLog({
workflow_run: createMockWorkflowRun({
diff --git a/web/app/components/app/workflow-log/detail.tsx b/web/app/components/app/workflow-log/detail.tsx
index 8bbe0cfd880..2d773e13260 100644
--- a/web/app/components/app/workflow-log/detail.tsx
+++ b/web/app/components/app/workflow-log/detail.tsx
@@ -3,25 +3,23 @@ import type { FC } from 'react'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiCloseLine, RiPlayLargeLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
-import { useStore } from '@/app/components/app/store'
import { WorkflowContextProvider } from '@/app/components/workflow/context'
import Run from '@/app/components/workflow/run'
import { useRouter } from '@/next/navigation'
type ILogDetail = {
+ appId: string
runID: string
onClose: () => void
canReplay?: boolean
}
-const DetailPanel: FC = ({ runID, onClose, canReplay = false }) => {
+const DetailPanel: FC = ({ appId, runID, onClose, canReplay = false }) => {
const { t } = useTranslation(['appLog', 'common'])
- const appDetail = useStore((state) => state.appDetail)
const router = useRouter()
const handleReplay = () => {
- if (!appDetail?.id) return
- router.push(`/app/${appDetail.id}/workflow?replayRunId=${runID}`)
+ router.push(`/app/${appId}/workflow?replayRunId=${runID}`)
}
return (
@@ -60,10 +58,8 @@ const DetailPanel: FC = ({ runID, onClose, canReplay = false }) => {