From b56d30b3661d1cc38626822eafc264166c5d3299 Mon Sep 17 00:00:00 2001 From: Jingyi Date: Tue, 25 Aug 2026 01:12:01 +0000 Subject: [PATCH] fix(web): refresh model provider version after update (#41212) --- .../__tests__/provider-card-actions.spec.tsx | 113 ++++++++++++++++-- .../provider-card-actions.tsx | 15 ++- 2 files changed, 112 insertions(+), 16 deletions(-) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx index 896815ac386..4c1a748b168 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx @@ -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) | 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 }) => ({ 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 - }) => ( -
- ), + onUpdatedFromMarketplace: () => Promise + }) => { + handleMarketplaceUpdateComplete = onUpdatedFromMarketplace + return ( +
+ ) + }, })) 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((resolve) => { + resolveRefresh = resolve + }) + const onUpdate = vi.fn(() => refreshPromise) + mockNormalizeInstalledPluginDetail.mockReturnValue(createDetail({ version: current })) + + const rendered = render( + , + ) + vi.spyOn(rendered.queryClient, 'fetchQuery').mockResolvedValue({ plugins: [{}] }) + + await user.click(screen.getByRole('button', { name: current })) + let updateComplete: Promise | undefined + await act(async () => { + updateComplete = handleMarketplaceUpdateComplete?.() + }) + await waitFor(() => expect(onUpdate).toHaveBeenCalledOnce()) + + rendered.rerender( + , + ) + + 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( + , + ) + 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 = { diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/provider-card-actions.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/provider-card-actions.tsx index 3afe75c3f7d..04dd5b9758e 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/provider-card-actions.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/provider-card-actions.tsx @@ -41,12 +41,12 @@ type Props = | Readonly<{ summary: ModelProviderPluginSummary providerLabel: string - onUpdate?: () => void + onUpdate?: () => void | Promise detail?: never }> | Readonly<{ detail: PluginDetail - onUpdate?: () => void + onUpdate?: () => void | Promise summary?: never providerLabel?: never }> @@ -79,7 +79,7 @@ const ProviderCardActions: FC = (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 ( setDetailAction(undefined)} - onUpdate={onUpdate} + onUpdate={handleLoadedDetailUpdated} /> ) } @@ -278,7 +283,7 @@ type LoadedProps = Readonly<{ detail: PluginDetail initialAction?: DetailAction onInitialActionHandled: () => void - onUpdate?: () => void + onUpdate?: () => void | Promise }> function LoadedProviderCardActions({