diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.spec.tsx index 6ed82ed095..95580a1c03 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.spec.tsx @@ -1,9 +1,17 @@ import type { ModelItem, ModelProvider } from '../declarations' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { disableModel, enableModel } from '@/service/common' import { ModelStatusEnum } from '../declarations' import ModelListItem from './model-list-item' +function createWrapper() { + const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }) + return ({ children }: { children: React.ReactNode }) => ( + {children} + ) +} + let mockModelLoadBalancingEnabled = false vi.mock('@/context/app-context', () => ({ @@ -69,6 +77,7 @@ describe('ModelListItem', () => { provider={mockProvider} isConfigurable={false} />, + { wrapper: createWrapper() }, ) expect(screen.getByTestId('model-icon')).toBeInTheDocument() expect(screen.getByTestId('model-name')).toBeInTheDocument() @@ -83,6 +92,7 @@ describe('ModelListItem', () => { isConfigurable={false} onChange={onChange} />, + { wrapper: createWrapper() }, ) fireEvent.click(screen.getByRole('switch')) @@ -102,6 +112,7 @@ describe('ModelListItem', () => { isConfigurable={false} onChange={onChange} />, + { wrapper: createWrapper() }, ) fireEvent.click(screen.getByRole('switch')) @@ -122,6 +133,7 @@ describe('ModelListItem', () => { isConfigurable={false} onModifyLoadBalancing={onModifyLoadBalancing} />, + { wrapper: createWrapper() }, ) fireEvent.click(screen.getByRole('button', { name: 'modify load balancing' })) 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 32ed348b47..13ae22ac55 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 @@ -81,11 +81,10 @@ const ProviderCardActions: FC = ({ detail, onUpdate }) => { pluginID={detail.plugin_id} currentVersion={version} onSelect={handleVersionSelect} - offset={{ mainAxis: 4, crossAxis: 0 }} + sideOffset={4} + alignOffset={0} trigger={( - + )} /> )} diff --git a/web/app/components/plugins/plugin-detail-panel/__tests__/detail-header.spec.tsx b/web/app/components/plugins/plugin-detail-panel/__tests__/detail-header.spec.tsx index a35fcec8be..f0ec5b6c83 100644 --- a/web/app/components/plugins/plugin-detail-panel/__tests__/detail-header.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/__tests__/detail-header.spec.tsx @@ -79,6 +79,10 @@ vi.mock('@/service/plugins', () => ({ uninstallPlugin: mockUninstallPlugin, })) +vi.mock('@/service/use-plugins', () => ({ + useInvalidateCheckInstalled: () => vi.fn(), +})) + vi.mock('@/service/use-tools', () => ({ useAllToolProviders: () => ({ data: [] }), useInvalidateAllToolProviders: () => mockInvalidateAllToolProviders, @@ -218,23 +222,6 @@ vi.mock('../../plugin-auth', () => ({ PluginAuth: () =>
, })) -// Mock Confirm component -vi.mock('@/app/components/base/confirm', () => ({ - default: ({ isShow, onCancel, onConfirm, isLoading }: { - isShow: boolean - onCancel: () => void - onConfirm: () => void - isLoading: boolean - }) => isShow - ? ( -
- - -
- ) - : null, -})) - const createPluginDetail = (overrides: Partial = {}): PluginDetail => ({ id: 'test-id', created_at: '2024-01-01', @@ -801,7 +788,7 @@ describe('DetailHeader', () => { fireEvent.click(screen.getByTestId('remove-btn')) await waitFor(() => { - expect(screen.getByTestId('delete-confirm')).toBeInTheDocument() + expect(screen.getByRole('alertdialog')).toBeInTheDocument() }) }) @@ -810,13 +797,13 @@ describe('DetailHeader', () => { fireEvent.click(screen.getByTestId('remove-btn')) await waitFor(() => { - expect(screen.getByTestId('delete-confirm')).toBeInTheDocument() + expect(screen.getByRole('alertdialog')).toBeInTheDocument() }) - fireEvent.click(screen.getByTestId('confirm-cancel')) + fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' })) await waitFor(() => { - expect(screen.queryByTestId('delete-confirm')).not.toBeInTheDocument() + expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() }) }) @@ -825,10 +812,10 @@ describe('DetailHeader', () => { fireEvent.click(screen.getByTestId('remove-btn')) await waitFor(() => { - expect(screen.getByTestId('delete-confirm')).toBeInTheDocument() + expect(screen.getByRole('alertdialog')).toBeInTheDocument() }) - fireEvent.click(screen.getByTestId('confirm-ok')) + fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' })) await waitFor(() => { expect(mockUninstallPlugin).toHaveBeenCalledWith('test-id') @@ -840,10 +827,10 @@ describe('DetailHeader', () => { fireEvent.click(screen.getByTestId('remove-btn')) await waitFor(() => { - expect(screen.getByTestId('delete-confirm')).toBeInTheDocument() + expect(screen.getByRole('alertdialog')).toBeInTheDocument() }) - fireEvent.click(screen.getByTestId('confirm-ok')) + fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' })) await waitFor(() => { expect(mockOnUpdate).toHaveBeenCalledWith(true) @@ -861,10 +848,10 @@ describe('DetailHeader', () => { fireEvent.click(screen.getByTestId('remove-btn')) await waitFor(() => { - expect(screen.getByTestId('delete-confirm')).toBeInTheDocument() + expect(screen.getByRole('alertdialog')).toBeInTheDocument() }) - fireEvent.click(screen.getByTestId('confirm-ok')) + fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' })) await waitFor(() => { expect(mockRefreshModelProviders).toHaveBeenCalled() @@ -876,10 +863,10 @@ describe('DetailHeader', () => { fireEvent.click(screen.getByTestId('remove-btn')) await waitFor(() => { - expect(screen.getByTestId('delete-confirm')).toBeInTheDocument() + expect(screen.getByRole('alertdialog')).toBeInTheDocument() }) - fireEvent.click(screen.getByTestId('confirm-ok')) + fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' })) await waitFor(() => { expect(mockInvalidateAllToolProviders).toHaveBeenCalled() @@ -891,10 +878,10 @@ describe('DetailHeader', () => { fireEvent.click(screen.getByTestId('remove-btn')) await waitFor(() => { - expect(screen.getByTestId('delete-confirm')).toBeInTheDocument() + expect(screen.getByRole('alertdialog')).toBeInTheDocument() }) - fireEvent.click(screen.getByTestId('confirm-ok')) + fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' })) await waitFor(() => { expect(amplitude.trackEvent).toHaveBeenCalledWith('plugin_uninstalled', expect.any(Object)) diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header/components/__tests__/header-modals.spec.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header/components/__tests__/header-modals.spec.tsx index 843800c190..004e6b94f0 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header/components/__tests__/header-modals.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header/components/__tests__/header-modals.spec.tsx @@ -9,24 +9,6 @@ vi.mock('@/context/i18n', () => ({ useGetLanguage: () => 'en_US', })) -vi.mock('@/app/components/base/confirm', () => ({ - default: ({ isShow, title, onCancel, onConfirm, isLoading }: { - isShow: boolean - title: string - onCancel: () => void - onConfirm: () => void - isLoading: boolean - }) => isShow - ? ( -
-
{title}
- - -
- ) - : null, -})) - vi.mock('@/app/components/plugins/plugin-page/plugin-info', () => ({ default: ({ repository, release, packageName, onHide }: { repository: string @@ -230,7 +212,7 @@ describe('HeaderModals', () => { />, ) - expect(screen.queryByTestId('delete-confirm')).not.toBeInTheDocument() + expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() }) it('should render delete confirm when isShowDeleteConfirm is true', () => { @@ -247,7 +229,7 @@ describe('HeaderModals', () => { />, ) - expect(screen.getByTestId('delete-confirm')).toBeInTheDocument() + expect(screen.getByRole('alertdialog')).toBeInTheDocument() }) it('should show correct delete title', () => { @@ -264,7 +246,7 @@ describe('HeaderModals', () => { />, ) - expect(screen.getByTestId('delete-title')).toHaveTextContent('plugin.action.delete') + expect(screen.getByRole('alertdialog')).toHaveTextContent('plugin.action.delete') }) it('should call hideDeleteConfirm when cancel is clicked', () => { @@ -281,7 +263,7 @@ describe('HeaderModals', () => { />, ) - fireEvent.click(screen.getByTestId('confirm-cancel')) + fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' })) expect(modalStates.hideDeleteConfirm).toHaveBeenCalled() }) @@ -300,7 +282,7 @@ describe('HeaderModals', () => { />, ) - fireEvent.click(screen.getByTestId('confirm-ok')) + fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' })) expect(mockOnDelete).toHaveBeenCalled() }) @@ -319,7 +301,7 @@ describe('HeaderModals', () => { />, ) - expect(screen.getByTestId('confirm-ok')).toBeDisabled() + expect(screen.getByRole('button', { name: /common\.operation\.confirm/ })).toBeDisabled() }) }) @@ -485,7 +467,7 @@ describe('HeaderModals', () => { ) expect(screen.getByTestId('plugin-info')).toBeInTheDocument() - expect(screen.getByTestId('delete-confirm')).toBeInTheDocument() + expect(screen.getByRole('alertdialog')).toBeInTheDocument() expect(screen.getByTestId('update-modal')).toBeInTheDocument() }) }) diff --git a/web/app/components/plugins/update-plugin/__tests__/index.spec.tsx b/web/app/components/plugins/update-plugin/__tests__/index.spec.tsx index 8a4b2187b5..eb13d466b8 100644 --- a/web/app/components/plugins/update-plugin/__tests__/index.spec.tsx +++ b/web/app/components/plugins/update-plugin/__tests__/index.spec.tsx @@ -104,36 +104,6 @@ vi.mock('../../install-plugin/install-from-github', () => ({ ), })) -// Mock Portal components for PluginVersionPicker -let mockPortalOpen = false -vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ - PortalToFollowElem: ({ children, open, onOpenChange: _onOpenChange }: { - children: React.ReactNode - open: boolean - onOpenChange: (open: boolean) => void - }) => { - mockPortalOpen = open - return
{children}
- }, - PortalToFollowElemTrigger: ({ children, onClick, className }: { - children: React.ReactNode - onClick: () => void - className?: string - }) => ( -
- {children} -
- ), - PortalToFollowElemContent: ({ children, className }: { - children: React.ReactNode - className?: string - }) => { - if (!mockPortalOpen) - return null - return
{children}
- }, -})) - // Mock semver vi.mock('semver', () => ({ lt: (v1: string, v2: string) => { @@ -247,7 +217,6 @@ const renderWithQueryClient = (ui: React.ReactElement) => { describe('update-plugin', () => { beforeEach(() => { vi.clearAllMocks() - mockPortalOpen = false mockCheck.mockResolvedValue({ status: TaskStatus.success }) }) @@ -964,7 +933,7 @@ describe('update-plugin', () => { render() // Assert - expect(screen.queryByTestId('portal-content')).not.toBeInTheDocument() + expect(screen.queryByText('plugin.detailPanel.switchVersion')).not.toBeInTheDocument() }) it('should render version list when isShow is true', () => { @@ -972,7 +941,6 @@ describe('update-plugin', () => { render() // Assert - expect(screen.getByTestId('portal-content')).toBeInTheDocument() expect(screen.getByText('plugin.detailPanel.switchVersion')).toBeInTheDocument() }) @@ -1002,7 +970,7 @@ describe('update-plugin', () => { // Act render() - fireEvent.click(screen.getByTestId('portal-trigger')) + fireEvent.click(screen.getByText('Select Version')) // Assert expect(onShowChange).toHaveBeenCalledWith(true) @@ -1014,7 +982,7 @@ describe('update-plugin', () => { // Act render() - fireEvent.click(screen.getByTestId('portal-trigger')) + fireEvent.click(screen.getByText('Select Version')) // Assert expect(onShowChange).not.toHaveBeenCalled() @@ -1116,7 +1084,7 @@ describe('update-plugin', () => { ) // Assert - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByText('plugin.detailPanel.switchVersion')).toBeInTheDocument() }) it('should support custom offset', () => { @@ -1125,12 +1093,13 @@ describe('update-plugin', () => { , ) // Assert - expect(screen.getByTestId('portal-elem')).toBeInTheDocument() + expect(screen.getByText('plugin.detailPanel.switchVersion')).toBeInTheDocument() }) }) diff --git a/web/app/components/plugins/update-plugin/from-market-place.tsx b/web/app/components/plugins/update-plugin/from-market-place.tsx index 18c9ac2eaf..8040103a7c 100644 --- a/web/app/components/plugins/update-plugin/from-market-place.tsx +++ b/web/app/components/plugins/update-plugin/from-market-place.tsx @@ -6,7 +6,12 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Badge, { BadgeState } from '@/app/components/base/badge/index' import Button from '@/app/components/base/button' -import Modal from '@/app/components/base/modal' +import { + Dialog, + DialogCloseButton, + DialogContent, + DialogTitle, +} from '@/app/components/base/ui/dialog' import Card from '@/app/components/plugins/card' import checkTaskStatus from '@/app/components/plugins/install-plugin/base/check-task-status' import { pluginManifestToCardPluginProps } from '@/app/components/plugins/install-plugin/utils' @@ -125,63 +130,65 @@ const UpdatePluginModal: FC = ({ const doShowDowngradeWarningModal = isShowDowngradeWarningModal && uploadStep === UploadStep.notStarted return ( - - {doShowDowngradeWarningModal && ( - - )} - {!doShowDowngradeWarningModal && ( - <> -
- {t(`${i18nPrefix}.description`, { ns: 'plugin' })} -
-
- - - {`${originalPackageInfo.payload.version} -> ${targetPackageInfo.version}`} - - + onCancel()}> + + + {doShowDowngradeWarningModal && ( + + )} + {!doShowDowngradeWarningModal && ( + <> + + {t(`${i18nPrefix}.${uploadStep === UploadStep.installed ? 'successfulTitle' : 'title'}`, { ns: 'plugin' })} + +
+ {t(`${i18nPrefix}.description`, { ns: 'plugin' })} +
+
+ + + {`${originalPackageInfo.payload.version} -> ${targetPackageInfo.version}`} + + + )} + /> +
+
+ {uploadStep === UploadStep.notStarted && ( + )} - /> -
-
- {uploadStep === UploadStep.notStarted && ( - )} - -
- - )} - - +
+ + )} + + ) } export default React.memo(UpdatePluginModal) diff --git a/web/app/components/plugins/update-plugin/plugin-version-picker.tsx b/web/app/components/plugins/update-plugin/plugin-version-picker.tsx index 88fff483d2..75b57006e2 100644 --- a/web/app/components/plugins/update-plugin/plugin-version-picker.tsx +++ b/web/app/components/plugins/update-plugin/plugin-version-picker.tsx @@ -1,19 +1,16 @@ 'use client' -import type { - OffsetOptions, - Placement, -} from '@floating-ui/react' import type { FC } from 'react' +import type { Placement } from '@/app/components/base/ui/placement' import * as React from 'react' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { lt } from 'semver' import Badge from '@/app/components/base/badge' import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' + Popover, + PopoverContent, + PopoverTrigger, +} from '@/app/components/base/ui/popover' import useTimestamp from '@/hooks/use-timestamp' import { useVersionListOfPlugin } from '@/service/use-plugins' import { cn } from '@/utils/classnames' @@ -26,7 +23,8 @@ type Props = { currentVersion: string trigger: React.ReactNode placement?: Placement - offset?: OffsetOptions + sideOffset?: number + alignOffset?: number onSelect: ({ version, unique_identifier, @@ -46,22 +44,14 @@ const PluginVersionPicker: FC = ({ currentVersion, trigger, placement = 'bottom-start', - offset = { - mainAxis: 4, - crossAxis: -16, - }, + sideOffset = 4, + alignOffset = -16, onSelect, }) => { const { t } = useTranslation() const format = t('dateTimeFormat', { ns: 'appLog' }).split(' ')[0] const { formatDate } = useTimestamp() - const handleTriggerClick = () => { - if (disabled) - return - onShowChange(!isShow) - } - const { data: res } = useVersionListOfPlugin(pluginID) const handleSelect = useCallback(({ version, unique_identifier, isDowngrade }: { @@ -76,49 +66,52 @@ const PluginVersionPicker: FC = ({ }, [currentVersion, onSelect, onShowChange]) return ( - { + if (!disabled) + onShowChange(open) + }} > - {trigger} - + - -
-
- {t('detailPanel.switchVersion', { ns: 'plugin' })} -
-
- {res?.data.versions.map(version => ( -
handleSelect({ - version: version.version, - unique_identifier: version.unique_identifier, - isDowngrade: lt(version.version, currentVersion), - })} - > -
-
{version.version}
- {currentVersion === version.version && } -
-
{formatDate(version.created_at, format)}
-
- ))} -
+ +
+ {t('detailPanel.switchVersion', { ns: 'plugin' })}
- - +
+ {res?.data.versions.map(version => ( +
handleSelect({ + version: version.version, + unique_identifier: version.unique_identifier, + isDowngrade: lt(version.version, currentVersion), + })} + > +
+
{version.version}
+ {currentVersion === version.version && } +
+
{formatDate(version.created_at, format)}
+
+ ))} +
+
+ ) }