mirror of
https://github.com/langgenius/dify.git
synced 2026-09-01 15:09:21 +08:00
fix(web): refresh model provider version after update (#41212)
This commit is contained in:
+102
-11
@@ -1,7 +1,7 @@
|
||||
import type { ReactElement } from 'react'
|
||||
import type { ModelProviderPluginSummary } from '../../index'
|
||||
import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { PluginSource } from '@/app/components/plugins/types'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
@@ -45,6 +45,7 @@ let mockHeaderState = {
|
||||
isFromMarketplace: true,
|
||||
isFromGitHub: false,
|
||||
}
|
||||
let handleMarketplaceUpdateComplete: (() => Promise<void>) | undefined
|
||||
|
||||
const render = (ui: ReactElement) =>
|
||||
renderWithConsoleQuery(ui, { systemFeatures: { enable_marketplace: true } })
|
||||
@@ -55,9 +56,12 @@ const openActionsMenu = () => {
|
||||
|
||||
vi.mock('@/app/components/plugins/plugin-detail-panel/detail-header/hooks', () => ({
|
||||
useDetailHeaderState: () => mockHeaderState,
|
||||
usePluginOperations: () => ({
|
||||
usePluginOperations: ({ onUpdate }: { onUpdate?: () => void | Promise<void> }) => ({
|
||||
handleUpdate: mockHandleUpdate,
|
||||
handleUpdatedFromMarketplace: mockHandleUpdatedFromMarketplace,
|
||||
handleUpdatedFromMarketplace: async () => {
|
||||
mockHandleUpdatedFromMarketplace()
|
||||
await onUpdate?.()
|
||||
},
|
||||
handleDelete: mockHandleDelete,
|
||||
}),
|
||||
}))
|
||||
@@ -67,18 +71,23 @@ vi.mock('@/app/components/plugins/plugin-detail-panel/detail-header/components',
|
||||
targetVersion,
|
||||
isDowngrade,
|
||||
isAutoUpgradeEnabled,
|
||||
onUpdatedFromMarketplace,
|
||||
}: {
|
||||
targetVersion?: { version: string; unique_identifier: string }
|
||||
isDowngrade: boolean
|
||||
isAutoUpgradeEnabled: boolean
|
||||
}) => (
|
||||
<div
|
||||
data-testid="header-modals"
|
||||
data-target-version={targetVersion?.version ?? ''}
|
||||
data-is-downgrade={String(isDowngrade)}
|
||||
data-auto-upgrade={String(isAutoUpgradeEnabled)}
|
||||
/>
|
||||
),
|
||||
onUpdatedFromMarketplace: () => Promise<void>
|
||||
}) => {
|
||||
handleMarketplaceUpdateComplete = onUpdatedFromMarketplace
|
||||
return (
|
||||
<div
|
||||
data-testid="header-modals"
|
||||
data-target-version={targetVersion?.version ?? ''}
|
||||
data-is-downgrade={String(isDowngrade)}
|
||||
data-auto-upgrade={String(isAutoUpgradeEnabled)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
|
||||
@@ -137,6 +146,7 @@ const createSummary = (
|
||||
describe('ProviderCardActions', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
handleMarketplaceUpdateComplete = undefined
|
||||
mockHeaderState = {
|
||||
modalStates: {
|
||||
showPluginInfo: mockShowPluginInfo,
|
||||
@@ -300,6 +310,87 @@ describe('ProviderCardActions', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
['upgrade', '1.0.0', '2.0.0'],
|
||||
['downgrade', '2.0.0', '1.0.0'],
|
||||
])(
|
||||
'should show the refreshed summary version after a marketplace %s',
|
||||
async (_, current, next) => {
|
||||
const user = userEvent.setup()
|
||||
let resolveRefresh: () => void = () => {}
|
||||
const refreshPromise = new Promise<void>((resolve) => {
|
||||
resolveRefresh = resolve
|
||||
})
|
||||
const onUpdate = vi.fn(() => refreshPromise)
|
||||
mockNormalizeInstalledPluginDetail.mockReturnValue(createDetail({ version: current }))
|
||||
|
||||
const rendered = render(
|
||||
<ProviderCardActions
|
||||
summary={createSummary({ source: 'marketplace', version: current })}
|
||||
providerLabel="Provider Plugin"
|
||||
onUpdate={onUpdate}
|
||||
/>,
|
||||
)
|
||||
vi.spyOn(rendered.queryClient, 'fetchQuery').mockResolvedValue({ plugins: [{}] })
|
||||
|
||||
await user.click(screen.getByRole('button', { name: current }))
|
||||
let updateComplete: Promise<void> | undefined
|
||||
await act(async () => {
|
||||
updateComplete = handleMarketplaceUpdateComplete?.()
|
||||
})
|
||||
await waitFor(() => expect(onUpdate).toHaveBeenCalledOnce())
|
||||
|
||||
rendered.rerender(
|
||||
<ProviderCardActions
|
||||
summary={createSummary({ source: 'marketplace', version: next })}
|
||||
providerLabel="Provider Plugin"
|
||||
onUpdate={onUpdate}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText(current)).toBeInTheDocument()
|
||||
expect(screen.getByTestId('header-modals')).toBeInTheDocument()
|
||||
|
||||
await act(async () => {
|
||||
resolveRefresh()
|
||||
await updateComplete
|
||||
})
|
||||
|
||||
expect(await screen.findByText(next)).toBeInTheDocument()
|
||||
expect(screen.queryByText(current)).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('header-modals')).not.toBeInTheDocument()
|
||||
},
|
||||
)
|
||||
|
||||
it('should keep the loaded detail when refreshing the summary fails', async () => {
|
||||
const user = userEvent.setup()
|
||||
const refreshError = new Error('refresh failed')
|
||||
const onUpdate = vi.fn().mockRejectedValue(refreshError)
|
||||
mockNormalizeInstalledPluginDetail.mockReturnValue(createDetail({ version: '1.0.0' }))
|
||||
const rendered = render(
|
||||
<ProviderCardActions
|
||||
summary={createSummary({ source: 'marketplace', version: '1.0.0' })}
|
||||
providerLabel="Provider Plugin"
|
||||
onUpdate={onUpdate}
|
||||
/>,
|
||||
)
|
||||
vi.spyOn(rendered.queryClient, 'fetchQuery').mockResolvedValue({ plugins: [{}] })
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '1.0.0' }))
|
||||
let thrownError: unknown
|
||||
await act(async () => {
|
||||
try {
|
||||
await handleMarketplaceUpdateComplete?.()
|
||||
} catch (error) {
|
||||
thrownError = error
|
||||
}
|
||||
})
|
||||
|
||||
expect(thrownError).toBe(refreshError)
|
||||
expect(screen.getByText('1.0.0')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('header-modals')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render version controls for marketplace plugins and handle manual version selection', async () => {
|
||||
const user = userEvent.setup()
|
||||
mockHeaderState = {
|
||||
|
||||
+10
-5
@@ -41,12 +41,12 @@ type Props =
|
||||
| Readonly<{
|
||||
summary: ModelProviderPluginSummary
|
||||
providerLabel: string
|
||||
onUpdate?: () => void
|
||||
onUpdate?: () => void | Promise<void>
|
||||
detail?: never
|
||||
}>
|
||||
| Readonly<{
|
||||
detail: PluginDetail
|
||||
onUpdate?: () => void
|
||||
onUpdate?: () => void | Promise<void>
|
||||
summary?: never
|
||||
providerLabel?: never
|
||||
}>
|
||||
@@ -79,7 +79,7 @@ const ProviderCardActions: FC<Props> = (props) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['marketplacePlugins'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['marketplaceCollectionPlugins'] }),
|
||||
])
|
||||
onUpdate?.()
|
||||
await onUpdate?.()
|
||||
}, [onUpdate, queryClient])
|
||||
|
||||
if (props.detail) {
|
||||
@@ -156,13 +156,18 @@ function SummaryProviderCardActions({ summary, providerLabel, onUpdate }: Summar
|
||||
}
|
||||
}
|
||||
|
||||
const handleLoadedDetailUpdated = useCallback(async () => {
|
||||
await onUpdate?.()
|
||||
setDetail(undefined)
|
||||
}, [onUpdate])
|
||||
|
||||
if (detail) {
|
||||
return (
|
||||
<LoadedProviderCardActions
|
||||
detail={detail}
|
||||
initialAction={detailAction}
|
||||
onInitialActionHandled={() => setDetailAction(undefined)}
|
||||
onUpdate={onUpdate}
|
||||
onUpdate={handleLoadedDetailUpdated}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -278,7 +283,7 @@ type LoadedProps = Readonly<{
|
||||
detail: PluginDetail
|
||||
initialAction?: DetailAction
|
||||
onInitialActionHandled: () => void
|
||||
onUpdate?: () => void
|
||||
onUpdate?: () => void | Promise<void>
|
||||
}>
|
||||
|
||||
function LoadedProviderCardActions({
|
||||
|
||||
Reference in New Issue
Block a user