mirror of
https://github.com/langgenius/dify.git
synced 2026-06-24 13:01:16 +08:00
fix(web): fix rebase
This commit is contained in:
parent
16a0cfc40c
commit
01957f302f
@ -5,6 +5,7 @@ import type { ModalContextState } from '@/context/modal-context'
|
||||
import type { ProviderContextState } from '@/context/provider-context'
|
||||
import type { IWorkspace } from '@/models/common'
|
||||
import type { InstalledApp } from '@/models/explore'
|
||||
import type { SnippetDetail, SnippetInputField } from '@/models/snippet'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { createStore, Provider as JotaiProvider } from 'jotai'
|
||||
import { createTestQueryClient, renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
|
||||
@ -16,6 +17,7 @@ import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/con
|
||||
import { useAppContext, useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { PipelineInputVarType } from '@/models/pipeline'
|
||||
import { usePathname, useRouter } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { useGetInstalledApps, useUninstallApp, useUpdateAppPinStatus } from '@/service/use-explore'
|
||||
@ -25,14 +27,28 @@ import { DETAIL_SIDEBAR_STORAGE_KEY } from '../storage'
|
||||
|
||||
const activeEdgeClassName = 'before:pointer-events-none'
|
||||
|
||||
const { mockIsAgentV2Enabled, mockSwitchWorkspace, mockToastSuccess, hotkeyRegistrations } = vi.hoisted(() => ({
|
||||
type SnippetNavigationTestState = {
|
||||
fields: SnippetInputField[]
|
||||
onFieldsChange?: (fields: SnippetInputField[]) => void
|
||||
readonly: boolean
|
||||
snippet?: SnippetDetail
|
||||
}
|
||||
|
||||
const { mockIsAgentV2Enabled, mockSnippetFieldsChange, mockSwitchWorkspace, mockToastSuccess, hotkeyRegistrations, snippetNavigationState } = vi.hoisted(() => ({
|
||||
mockSwitchWorkspace: vi.fn(),
|
||||
mockSnippetFieldsChange: vi.fn(),
|
||||
mockToastSuccess: vi.fn(),
|
||||
mockIsAgentV2Enabled: vi.fn(() => true),
|
||||
hotkeyRegistrations: new Map<string, {
|
||||
handler: (event: { preventDefault: () => void }) => void
|
||||
options?: { ignoreInputs?: boolean }
|
||||
}>(),
|
||||
snippetNavigationState: {
|
||||
fields: [],
|
||||
readonly: true,
|
||||
snippet: undefined,
|
||||
onFieldsChange: undefined,
|
||||
} as SnippetNavigationTestState,
|
||||
}))
|
||||
|
||||
vi.mock('@/features/agent-v2/feature-flag', () => ({
|
||||
@ -184,6 +200,38 @@ vi.mock('@/features/deployments/detail/deployment-sidebar', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/snippets/store', () => ({
|
||||
useSnippetDetailStore: (selector: (state: SnippetNavigationTestState) => unknown) => selector(snippetNavigationState),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/snippets/components/snippet-sidebar', () => ({
|
||||
SnippetSidebarContent: ({
|
||||
fields,
|
||||
onFieldsChange,
|
||||
readonly,
|
||||
snippet,
|
||||
}: {
|
||||
fields: SnippetInputField[]
|
||||
onFieldsChange: (fields: SnippetInputField[]) => void
|
||||
readonly: boolean
|
||||
snippet: SnippetDetail
|
||||
}) => (
|
||||
<div data-testid="snippet-sidebar-content" data-readonly={String(readonly)}>
|
||||
<span>{snippet.name}</span>
|
||||
<span>{fields.map(field => field.variable).join(',')}</span>
|
||||
<button type="button" onClick={() => onFieldsChange([])}>change snippet fields</button>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('../components/snippet-detail-top', () => ({
|
||||
default: ({ expand, onToggle }: { expand: boolean, onToggle: () => void }) => (
|
||||
<div data-testid="snippet-detail-top" data-expand={expand}>
|
||||
<button type="button" data-testid="snippet-detail-toggle" onClick={onToggle}>Toggle</button>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useLocale: () => 'en-US',
|
||||
useDocLink: () => (path: string) => `https://docs.dify.ai${path}`,
|
||||
@ -241,6 +289,24 @@ const createInstalledApp = (overrides: Partial<InstalledApp> = {}): InstalledApp
|
||||
},
|
||||
})
|
||||
|
||||
const snippet: SnippetDetail = {
|
||||
id: 'snippet-1',
|
||||
name: 'Snippet',
|
||||
description: 'Description',
|
||||
updatedAt: '2026-03-29 10:00',
|
||||
usage: '0',
|
||||
tags: [],
|
||||
}
|
||||
|
||||
const snippetFields: SnippetInputField[] = [
|
||||
{
|
||||
label: 'Query',
|
||||
variable: 'query',
|
||||
type: PipelineInputVarType.textInput,
|
||||
required: true,
|
||||
},
|
||||
]
|
||||
|
||||
const appContextValue: AppContextValue = {
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
@ -357,6 +423,10 @@ describe('MainNav', () => {
|
||||
})
|
||||
mockSwitchWorkspace.mockReturnValue(new Promise(() => {}))
|
||||
hotkeyRegistrations.clear()
|
||||
snippetNavigationState.fields = []
|
||||
snippetNavigationState.onFieldsChange = undefined
|
||||
snippetNavigationState.readonly = true
|
||||
snippetNavigationState.snippet = undefined
|
||||
useAppStore.getState().setAppDetail()
|
||||
})
|
||||
|
||||
@ -562,12 +632,24 @@ describe('MainNav', () => {
|
||||
expect(screen.getByRole('link', { name: /common.mainNav.home/ })).not.toHaveAttribute('aria-current')
|
||||
})
|
||||
|
||||
it('hides the main menu on snippet detail routes while keeping account settings available', () => {
|
||||
it('replaces global navigation with snippet detail navigation on snippet routes', () => {
|
||||
mockPathname = '/snippets/snippet-1/orchestrate'
|
||||
snippetNavigationState.fields = snippetFields
|
||||
snippetNavigationState.onFieldsChange = mockSnippetFieldsChange
|
||||
snippetNavigationState.readonly = false
|
||||
snippetNavigationState.snippet = snippet
|
||||
|
||||
renderMainNav()
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-16')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-[248px]')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('p-1')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('bg-background-body')
|
||||
expect(screen.getByTestId('snippet-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('snippet-sidebar-content')).toHaveAttribute('data-readonly', 'false')
|
||||
expect(screen.getByText(snippet.name)).toBeInTheDocument()
|
||||
expect(screen.getByText('query')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'change snippet fields' }))
|
||||
expect(mockSnippetFieldsChange).toHaveBeenCalledWith([])
|
||||
expect(screen.queryByLabelText('Dify')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'common.mainNav.workspace.openMenu' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('link', { name: /common.mainNav.home/ })).not.toBeInTheDocument()
|
||||
@ -577,6 +659,22 @@ describe('MainNav', () => {
|
||||
expect(screen.getByRole('button', { name: 'common.mainNav.help.openMenu' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('collapses snippet detail navigation from the top-right toggle', () => {
|
||||
mockPathname = '/snippets/snippet-1/orchestrate'
|
||||
snippetNavigationState.fields = snippetFields
|
||||
snippetNavigationState.onFieldsChange = mockSnippetFieldsChange
|
||||
snippetNavigationState.snippet = snippet
|
||||
|
||||
renderMainNav()
|
||||
fireEvent.click(screen.getByTestId('snippet-detail-toggle'))
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-16')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('p-1')
|
||||
expect(screen.getByTestId('snippet-detail-top')).toHaveAttribute('data-expand', 'false')
|
||||
expect(screen.queryByTestId('snippet-sidebar-content')).not.toBeInTheDocument()
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
})
|
||||
|
||||
it('replaces global navigation with app detail navigation on app routes', () => {
|
||||
mockPathname = '/app/app-1/overview'
|
||||
|
||||
|
||||
@ -14,9 +14,8 @@ import DatasetDetailTop from '@/app/components/app-sidebar/dataset-detail-top'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import EnvNav from '@/app/components/header/env-nav'
|
||||
// import { buildIntegrationPath } from '@/app/components/integrations/routes'
|
||||
// import { SnippetSidebarContent } from '@/app/components/snippets/components/snippet-sidebar'
|
||||
// import { useSnippetDetailStore } from '@/app/components/snippets/store'
|
||||
import { SnippetSidebarContent } from '@/app/components/snippets/components/snippet-sidebar'
|
||||
import { useSnippetDetailStore } from '@/app/components/snippets/store'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { AgentDetailSection, AgentDetailTop } from '@/features/agent-v2/agent-detail/navigation'
|
||||
import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag'
|
||||
@ -28,7 +27,7 @@ import AccountSection from './components/account-section'
|
||||
import HelpMenu from './components/help-menu'
|
||||
import MainNavLink from './components/nav-link'
|
||||
import { MainNavSearchButton } from './components/search-button'
|
||||
// import SnippetDetailTop from './components/snippet-detail-top'
|
||||
import SnippetDetailTop from './components/snippet-detail-top'
|
||||
import WebAppsSection from './components/web-apps-section'
|
||||
import { WorkspaceCard } from './components/workspace-card'
|
||||
import { isMainNavRouteVisible, MAIN_NAV_ROUTES } from './routes'
|
||||
@ -99,8 +98,14 @@ const MainNav = ({
|
||||
const showDatasetDetailNavigation = isDatasetDetailPathname(pathname)
|
||||
const showAgentDetailNavigation = agentV2Enabled && !isCurrentWorkspaceDatasetOperator && isAgentDetailPathname(pathname)
|
||||
const showDeploymentDetailNavigation = canUseAppDeploy && !isCurrentWorkspaceDatasetOperator && isDeploymentDetailPathname(pathname)
|
||||
const showSnippetDetailBottomNavigation = isSnippetDetailPathname(pathname)
|
||||
const showDetailNavigation = showAppDetailNavigation || showDatasetDetailNavigation || showAgentDetailNavigation || showDeploymentDetailNavigation
|
||||
const showSnippetDetailNavigation = isSnippetDetailPathname(pathname)
|
||||
const showDetailNavigation = showAppDetailNavigation || showDatasetDetailNavigation || showAgentDetailNavigation || showDeploymentDetailNavigation || showSnippetDetailNavigation
|
||||
const snippetNavigation = useSnippetDetailStore(useShallow(state => ({
|
||||
fields: state.fields,
|
||||
onFieldsChange: state.onFieldsChange,
|
||||
readonly: state.readonly,
|
||||
snippet: state.snippet,
|
||||
})))
|
||||
const { hasAppDetail, setAppDetail } = useAppStore(useShallow(state => ({
|
||||
hasAppDetail: !!state.appDetail,
|
||||
setAppDetail: state.setAppDetail,
|
||||
@ -265,25 +270,30 @@ const MainNav = ({
|
||||
onToggle={handleToggleDetailNavigation}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<DeploymentDetailTop
|
||||
expand={detailNavigationVisibleExpanded}
|
||||
onToggle={handleToggleDetailNavigation}
|
||||
/>
|
||||
)
|
||||
: showSnippetDetailBottomNavigation
|
||||
? null
|
||||
: (
|
||||
<>
|
||||
<div className="flex items-center justify-between pt-3 pr-2 pb-2 pl-4">
|
||||
{renderLogo()}
|
||||
<MainNavSearchButton />
|
||||
</div>
|
||||
<div className="p-2">
|
||||
<WorkspaceCard />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
: showDeploymentDetailNavigation
|
||||
? (
|
||||
<DeploymentDetailTop
|
||||
expand={detailNavigationVisibleExpanded}
|
||||
onToggle={handleToggleDetailNavigation}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<SnippetDetailTop
|
||||
expand={detailNavigationVisibleExpanded}
|
||||
onToggle={handleToggleDetailNavigation}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<div className="flex items-center justify-between pt-3 pr-2 pb-2 pl-4">
|
||||
{renderLogo()}
|
||||
<MainNavSearchButton />
|
||||
</div>
|
||||
<div className="p-2">
|
||||
<WorkspaceCard />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{showDetailNavigation
|
||||
? showAppDetailNavigation
|
||||
? <AppDetailSection expand={detailNavigationVisibleExpanded} />
|
||||
@ -291,20 +301,29 @@ const MainNav = ({
|
||||
? <DatasetDetailSection expand={detailNavigationVisibleExpanded} />
|
||||
: showAgentDetailNavigation
|
||||
? <AgentDetailSection expand={detailNavigationVisibleExpanded} />
|
||||
: <DeploymentDetailSection expand={detailNavigationVisibleExpanded} />
|
||||
: showSnippetDetailBottomNavigation
|
||||
? null
|
||||
: (
|
||||
<>
|
||||
<nav className="flex flex-col gap-px p-2">
|
||||
{navItems.map(item => (
|
||||
<MainNavLink key={item.href} item={item} pathname={pathname} />
|
||||
))}
|
||||
</nav>
|
||||
{!isCurrentWorkspaceDatasetOperator && <WebAppsSection />}
|
||||
</>
|
||||
)}
|
||||
{showEnvTag && !showSnippetDetailBottomNavigation && detailNavigationVisibleExpanded && (
|
||||
: showDeploymentDetailNavigation
|
||||
? <DeploymentDetailSection expand={detailNavigationVisibleExpanded} />
|
||||
: detailNavigationVisibleExpanded && snippetNavigation.snippet && snippetNavigation.onFieldsChange
|
||||
? (
|
||||
<SnippetSidebarContent
|
||||
snippet={snippetNavigation.snippet}
|
||||
fields={snippetNavigation.fields}
|
||||
readonly={snippetNavigation.readonly}
|
||||
onFieldsChange={snippetNavigation.onFieldsChange}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
: (
|
||||
<>
|
||||
<nav className="flex flex-col gap-px p-2">
|
||||
{navItems.map(item => (
|
||||
<MainNavLink key={item.href} item={item} pathname={pathname} />
|
||||
))}
|
||||
</nav>
|
||||
{!isCurrentWorkspaceDatasetOperator && <WebAppsSection />}
|
||||
</>
|
||||
)}
|
||||
{showEnvTag && detailNavigationVisibleExpanded && (
|
||||
<div className="relative z-30 mt-auto shrink-0 px-3 pb-2">
|
||||
<EnvNav />
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user