From b0920ecd17743a55638bded693e3d93237ebfcc5 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Tue, 24 Mar 2026 14:02:52 +0800 Subject: [PATCH] refactor(web): migrate plugin toast usage to new UI toast API and update tests (#34001) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../plugins/plugin-install-flow.test.ts | 12 +++- .../install-plugin/__tests__/hooks.spec.ts | 20 ++++--- .../plugins/install-plugin/hooks.ts | 20 ++----- .../__tests__/index.spec.tsx | 14 +++-- .../install-from-github/index.tsx | 21 ++----- .../__tests__/detail-header.spec.tsx | 19 ++++++- .../__tests__/endpoint-card.spec.tsx | 19 ++++++- .../__tests__/endpoint-modal.spec.tsx | 20 ++++++- .../__tests__/use-plugin-operations.spec.ts | 21 ++++++- .../hooks/use-plugin-operations.ts | 14 ++--- .../plugin-detail-panel/endpoint-card.tsx | 20 +++---- .../plugin-detail-panel/endpoint-list.tsx | 12 ++-- .../plugin-detail-panel/endpoint-modal.tsx | 13 +++-- .../model-selector/__tests__/index.spec.tsx | 32 +++++++---- .../model-selector/index.tsx | 9 +-- .../__tests__/log-viewer.spec.tsx | 20 +++++-- .../__tests__/selector-entry.spec.tsx | 14 +++-- .../__tests__/selector-view.spec.tsx | 14 ++++- .../__tests__/subscription-card.spec.tsx | 14 ++++- .../create/__tests__/common-modal.spec.tsx | 14 +++-- .../create/__tests__/index.spec.tsx | 21 ++++--- .../create/__tests__/oauth-client.spec.tsx | 17 ++++-- .../__tests__/use-oauth-client-state.spec.ts | 17 ++++-- .../create/hooks/use-common-modal-state.ts | 42 +++----------- .../create/hooks/use-oauth-client-state.ts | 27 ++------- .../subscription-list/create/index.tsx | 12 +--- .../subscription-list/create/oauth-client.tsx | 7 +-- .../edit/__tests__/apikey-edit-modal.spec.tsx | 16 ++++-- .../edit/__tests__/index.spec.tsx | 12 +++- .../edit/__tests__/manual-edit-modal.spec.tsx | 16 ++++-- .../edit/__tests__/oauth-edit-modal.spec.tsx | 16 ++++-- .../edit/apikey-edit-modal.tsx | 24 ++------ .../edit/manual-edit-modal.tsx | 12 +--- .../edit/oauth-edit-modal.tsx | 12 +--- .../subscription-list/log-viewer.tsx | 7 +-- .../tool-selector/__tests__/index.spec.tsx | 14 ++++- .../__tests__/tool-credentials-form.spec.tsx | 16 ++++-- .../components/tool-credentials-form.tsx | 7 ++- .../plugin-item/__tests__/action.spec.tsx | 31 ++++++---- .../components/plugins/plugin-item/action.tsx | 4 +- web/eslint-suppressions.json | 57 +++---------------- 41 files changed, 390 insertions(+), 339 deletions(-) diff --git a/web/__tests__/plugins/plugin-install-flow.test.ts b/web/__tests__/plugins/plugin-install-flow.test.ts index 8edb6705d4..8fa2246198 100644 --- a/web/__tests__/plugins/plugin-install-flow.test.ts +++ b/web/__tests__/plugins/plugin-install-flow.test.ts @@ -12,8 +12,16 @@ vi.mock('@/config', () => ({ })) const mockToastNotify = vi.fn() -vi.mock('@/app/components/base/toast', () => ({ - default: { notify: (...args: unknown[]) => mockToastNotify(...args) }, +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign((message: string, options?: { type?: string }) => mockToastNotify({ type: options?.type, message }), { + success: (message: string) => mockToastNotify({ type: 'success', message }), + error: (message: string) => mockToastNotify({ type: 'error', message }), + warning: (message: string) => mockToastNotify({ type: 'warning', message }), + info: (message: string) => mockToastNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), })) const mockUploadGitHub = vi.fn() diff --git a/web/app/components/plugins/install-plugin/__tests__/hooks.spec.ts b/web/app/components/plugins/install-plugin/__tests__/hooks.spec.ts index 918a9b36e3..6b0fc27adf 100644 --- a/web/app/components/plugins/install-plugin/__tests__/hooks.spec.ts +++ b/web/app/components/plugins/install-plugin/__tests__/hooks.spec.ts @@ -3,8 +3,16 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { useGitHubReleases, useGitHubUpload } from '../hooks' const mockNotify = vi.fn() -vi.mock('@/app/components/base/toast', () => ({ - default: { notify: (...args: unknown[]) => mockNotify(...args) }, +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign((...args: unknown[]) => mockNotify(...args), { + success: (...args: unknown[]) => mockNotify(...args), + error: (...args: unknown[]) => mockNotify(...args), + warning: (...args: unknown[]) => mockNotify(...args), + info: (...args: unknown[]) => mockNotify(...args), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), })) vi.mock('@/config', () => ({ @@ -56,9 +64,7 @@ describe('install-plugin/hooks', () => { const releases = await result.current.fetchReleases('owner', 'repo') expect(releases).toEqual([]) - expect(mockNotify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'error' }), - ) + expect(mockNotify).toHaveBeenCalledWith('Failed to fetch repository releases') }) }) @@ -130,9 +136,7 @@ describe('install-plugin/hooks', () => { await expect( result.current.handleUpload('url', 'v1', 'pkg'), ).rejects.toThrow('Upload failed') - expect(mockNotify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'error', message: 'Error uploading package' }), - ) + expect(mockNotify).toHaveBeenCalledWith('Error uploading package') }) }) }) diff --git a/web/app/components/plugins/install-plugin/hooks.ts b/web/app/components/plugins/install-plugin/hooks.ts index 2addba4a04..cc7148cc17 100644 --- a/web/app/components/plugins/install-plugin/hooks.ts +++ b/web/app/components/plugins/install-plugin/hooks.ts @@ -1,6 +1,5 @@ import type { GitHubRepoReleaseResponse } from '../types' -import type { IToastProps } from '@/app/components/base/toast' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { GITHUB_ACCESS_TOKEN } from '@/config' import { uploadGitHub } from '@/service/plugins' import { compareVersion, getLatestVersion } from '@/utils/semver' @@ -37,16 +36,10 @@ export const useGitHubReleases = () => { } catch (error) { if (error instanceof Error) { - Toast.notify({ - type: 'error', - message: error.message, - }) + toast.error(error.message) } else { - Toast.notify({ - type: 'error', - message: 'Failed to fetch repository releases', - }) + toast.error('Failed to fetch repository releases') } return [] } @@ -54,7 +47,7 @@ export const useGitHubReleases = () => { const checkForUpdates = (fetchedReleases: GitHubRepoReleaseResponse[], currentVersion: string) => { let needUpdate = false - const toastProps: IToastProps = { + const toastProps: { type?: 'success' | 'error' | 'info' | 'warning', message: string } = { type: 'info', message: 'No new version available', } @@ -99,10 +92,7 @@ export const useGitHubUpload = () => { return GitHubPackage } catch (error) { - Toast.notify({ - type: 'error', - message: 'Error uploading package', - }) + toast.error('Error uploading package') throw error } } diff --git a/web/app/components/plugins/install-plugin/install-from-github/__tests__/index.spec.tsx b/web/app/components/plugins/install-plugin/install-from-github/__tests__/index.spec.tsx index 0fe6b88ed8..8abec7817b 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/__tests__/index.spec.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/__tests__/index.spec.tsx @@ -57,10 +57,16 @@ const createUpdatePayload = (overrides: Partial = {}): // Mock external dependencies const mockNotify = vi.fn() -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: (props: { type: string, message: string }) => mockNotify(props), - }, +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign((props: { type: string, message: string }) => mockNotify(props), { + success: (message: string) => mockNotify({ type: 'success', message }), + error: (message: string) => mockNotify({ type: 'error', message }), + warning: (message: string) => mockNotify({ type: 'warning', message }), + info: (message: string) => mockNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), })) const mockGetIconUrl = vi.fn() diff --git a/web/app/components/plugins/install-plugin/install-from-github/index.tsx b/web/app/components/plugins/install-plugin/install-from-github/index.tsx index 91425031cf..ff51698478 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/index.tsx @@ -7,7 +7,7 @@ import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Modal from '@/app/components/base/modal' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon' import { cn } from '@/utils/classnames' import { InstallStepFromGitHub } from '../../types' @@ -81,10 +81,7 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on const handleUrlSubmit = async () => { const { isValid, owner, repo } = parseGitHubUrl(state.repoUrl) if (!isValid || !owner || !repo) { - Toast.notify({ - type: 'error', - message: t('error.inValidGitHubUrl', { ns: 'plugin' }), - }) + toast.error(t('error.inValidGitHubUrl', { ns: 'plugin' })) return } try { @@ -97,17 +94,11 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on })) } else { - Toast.notify({ - type: 'error', - message: t('error.noReleasesFound', { ns: 'plugin' }), - }) + toast.error(t('error.noReleasesFound', { ns: 'plugin' })) } } catch { - Toast.notify({ - type: 'error', - message: t('error.fetchReleasesError', { ns: 'plugin' }), - }) + toast.error(t('error.fetchReleasesError', { ns: 'plugin' })) } } @@ -175,10 +166,10 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on >
-
+
{getTitle()}
-
+
{!([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step)) && t('installFromGitHub.installNote', { ns: 'plugin' })}
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 f0ec5b6c83..f8d6488128 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 @@ -2,10 +2,25 @@ import type { PluginDetail } from '../../types' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import * as amplitude from '@/app/components/base/amplitude' -import Toast from '@/app/components/base/toast' import { PluginSource } from '../../types' import DetailHeader from '../detail-header' +const { mockToast } = vi.hoisted(() => ({ + mockToast: Object.assign(vi.fn(), { + success: vi.fn(), + error: vi.fn(), + warning: vi.fn(), + info: vi.fn(), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), +})) + +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: mockToast, +})) + const { mockSetShowUpdatePluginModal, mockRefreshModelProviders, @@ -272,7 +287,7 @@ describe('DetailHeader', () => { vi.clearAllMocks() mockAutoUpgradeInfo = null mockEnableMarketplace = true - vi.spyOn(Toast, 'notify').mockImplementation(() => ({ clear: vi.fn() })) + vi.clearAllMocks() vi.spyOn(amplitude, 'trackEvent').mockImplementation(() => {}) }) diff --git a/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-card.spec.tsx b/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-card.spec.tsx index 480f399c91..237c72adf0 100644 --- a/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-card.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-card.spec.tsx @@ -1,7 +1,6 @@ import type { EndpointListItem, PluginDetail } from '../../types' import { act, fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import Toast from '@/app/components/base/toast' import EndpointCard from '../endpoint-card' const mockHandleChange = vi.fn() @@ -9,6 +8,22 @@ const mockEnableEndpoint = vi.fn() const mockDisableEndpoint = vi.fn() const mockDeleteEndpoint = vi.fn() const mockUpdateEndpoint = vi.fn() +const mockToastNotify = vi.fn() + +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign( + (message: string, options?: { type?: string }) => mockToastNotify({ type: options?.type, message }), + { + success: (message: string) => mockToastNotify({ type: 'success', message }), + error: (message: string) => mockToastNotify({ type: 'error', message }), + warning: (message: string) => mockToastNotify({ type: 'warning', message }), + info: (message: string) => mockToastNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }, + ), +})) // Flags to control whether operations should fail const failureFlags = { @@ -127,8 +142,6 @@ describe('EndpointCard', () => { failureFlags.disable = false failureFlags.delete = false failureFlags.update = false - // Mock Toast.notify to prevent toast elements from accumulating in DOM - vi.spyOn(Toast, 'notify').mockImplementation(() => ({ clear: vi.fn() })) // Polyfill document.execCommand for copy-to-clipboard in jsdom if (typeof document.execCommand !== 'function') { document.execCommand = vi.fn().mockReturnValue(true) diff --git a/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-modal.spec.tsx b/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-modal.spec.tsx index 1dfe31c6b1..a467de7142 100644 --- a/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-modal.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-modal.spec.tsx @@ -2,9 +2,25 @@ import type { FormSchema } from '../../../base/form/types' import type { PluginDetail } from '../../types' import { fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import Toast from '@/app/components/base/toast' import EndpointModal from '../endpoint-modal' +const mockToastNotify = vi.fn() + +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign( + (message: string, options?: { type?: string }) => mockToastNotify({ type: options?.type, message }), + { + success: (message: string) => mockToastNotify({ type: 'success', message }), + error: (message: string) => mockToastNotify({ type: 'error', message }), + warning: (message: string) => mockToastNotify({ type: 'warning', message }), + info: (message: string) => mockToastNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }, + ), +})) + vi.mock('@/hooks/use-i18n', () => ({ useRenderI18nObject: () => (obj: Record | string) => typeof obj === 'string' ? obj : obj?.en_US || '', @@ -69,11 +85,9 @@ const mockPluginDetail: PluginDetail = { describe('EndpointModal', () => { const mockOnCancel = vi.fn() const mockOnSaved = vi.fn() - let mockToastNotify: ReturnType beforeEach(() => { vi.clearAllMocks() - mockToastNotify = vi.spyOn(Toast, 'notify').mockImplementation(() => ({ clear: vi.fn() })) }) describe('Rendering', () => { diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/__tests__/use-plugin-operations.spec.ts b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/__tests__/use-plugin-operations.spec.ts index 0fcec7f16b..77d41c5bce 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/__tests__/use-plugin-operations.spec.ts +++ b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/__tests__/use-plugin-operations.spec.ts @@ -3,7 +3,6 @@ import type { ModalStates, VersionTarget } from '../use-detail-header-state' import { act, renderHook } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import * as amplitude from '@/app/components/base/amplitude' -import Toast from '@/app/components/base/toast' import { PluginSource } from '../../../../types' import { usePluginOperations } from '../use-plugin-operations' @@ -20,6 +19,7 @@ const { mockUninstallPlugin, mockFetchReleases, mockCheckForUpdates, + mockToastNotify, } = vi.hoisted(() => { return { mockSetShowUpdatePluginModal: vi.fn(), @@ -29,9 +29,25 @@ const { mockUninstallPlugin: vi.fn(() => Promise.resolve({ success: true })), mockFetchReleases: vi.fn(() => Promise.resolve([{ tag_name: 'v2.0.0' }])), mockCheckForUpdates: vi.fn(() => ({ needUpdate: true, toastProps: { type: 'success', message: 'Update available' } })), + mockToastNotify: vi.fn(), } }) +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign( + (message: string, options?: { type?: string }) => mockToastNotify({ type: options?.type, message }), + { + success: (message: string) => mockToastNotify({ type: 'success', message }), + error: (message: string) => mockToastNotify({ type: 'error', message }), + warning: (message: string) => mockToastNotify({ type: 'warning', message }), + info: (message: string) => mockToastNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }, + ), +})) + vi.mock('@/context/modal-context', () => ({ useModalContext: () => ({ setShowUpdatePluginModal: mockSetShowUpdatePluginModal, @@ -124,7 +140,6 @@ describe('usePluginOperations', () => { modalStates = createModalStatesMock() versionPicker = createVersionPickerMock() mockOnUpdate = vi.fn() - vi.spyOn(Toast, 'notify').mockImplementation(() => ({ clear: vi.fn() })) vi.spyOn(amplitude, 'trackEvent').mockImplementation(() => {}) }) @@ -233,7 +248,7 @@ describe('usePluginOperations', () => { }) expect(mockCheckForUpdates).toHaveBeenCalled() - expect(Toast.notify).toHaveBeenCalled() + expect(mockToastNotify).toHaveBeenCalledWith({ type: 'success', message: 'Update available' }) }) it('should show update plugin modal when update is needed', async () => { diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts index bf6bb4aae6..ade47cec5f 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts +++ b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts @@ -5,7 +5,7 @@ import type { ModalStates, VersionTarget } from './use-detail-header-state' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { trackEvent } from '@/app/components/base/amplitude' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' import { uninstallPlugin } from '@/service/plugins' @@ -60,10 +60,7 @@ export const usePluginOperations = ({ } if (!meta?.repo || !meta?.version || !meta?.package) { - Toast.notify({ - type: 'error', - message: 'Missing plugin metadata for GitHub update', - }) + toast.error('Missing plugin metadata for GitHub update') return } @@ -74,7 +71,7 @@ export const usePluginOperations = ({ return const { needUpdate, toastProps } = checkForUpdates(fetchedReleases, meta.version) - Toast.notify(toastProps) + toast(toastProps.message, { type: toastProps.type }) if (needUpdate) { setShowUpdatePluginModal({ @@ -122,10 +119,7 @@ export const usePluginOperations = ({ if (res.success) { modalStates.hideDeleteConfirm() - Toast.notify({ - type: 'success', - message: t('action.deleteSuccess', { ns: 'plugin' }), - }) + toast.success(t('action.deleteSuccess', { ns: 'plugin' })) handlePluginUpdated(true) if (PluginCategoryEnum.model.includes(category)) diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx index 164bab0f04..9f95d9c7e1 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx @@ -9,8 +9,8 @@ import ActionButton from '@/app/components/base/action-button' import Confirm from '@/app/components/base/confirm' import { CopyCheck } from '@/app/components/base/icons/src/vender/line/files' import Switch from '@/app/components/base/switch' -import Toast from '@/app/components/base/toast' import Tooltip from '@/app/components/base/tooltip' +import { toast } from '@/app/components/base/ui/toast' import Indicator from '@/app/components/header/indicator' import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import { @@ -47,7 +47,7 @@ const EndpointCard = ({ await handleChange() }, onError: () => { - Toast.notify({ type: 'error', message: t('actionMsg.modifiedUnsuccessfully', { ns: 'common' }) }) + toast.error(t('actionMsg.modifiedUnsuccessfully', { ns: 'common' })) setActive(false) }, }) @@ -57,7 +57,7 @@ const EndpointCard = ({ hideDisableConfirm() }, onError: () => { - Toast.notify({ type: 'error', message: t('actionMsg.modifiedUnsuccessfully', { ns: 'common' }) }) + toast.error(t('actionMsg.modifiedUnsuccessfully', { ns: 'common' })) setActive(false) }, }) @@ -83,7 +83,7 @@ const EndpointCard = ({ hideDeleteConfirm() }, onError: () => { - Toast.notify({ type: 'error', message: t('actionMsg.modifiedUnsuccessfully', { ns: 'common' }) }) + toast.error(t('actionMsg.modifiedUnsuccessfully', { ns: 'common' })) }, }) @@ -108,7 +108,7 @@ const EndpointCard = ({ hideEndpointModalConfirm() }, onError: () => { - Toast.notify({ type: 'error', message: t('actionMsg.modifiedUnsuccessfully', { ns: 'common' }) }) + toast.error(t('actionMsg.modifiedUnsuccessfully', { ns: 'common' })) }, }) const handleUpdate = (state: Record) => updateEndpoint({ @@ -139,7 +139,7 @@ const EndpointCard = ({
-
+
{data.name}
@@ -154,8 +154,8 @@ const EndpointCard = ({
{data.declaration.endpoints.filter(endpoint => !endpoint.hidden).map((endpoint, index) => (
-
{endpoint.method}
-
+
{endpoint.method}
+
{`${data.url}${endpoint.path}`}
handleCopy(`${data.url}${endpoint.path}`)}> @@ -168,13 +168,13 @@ const EndpointCard = ({
{active && ( -
+
{t('detailPanel.serviceOk', { ns: 'plugin' })}
)} {!active && ( -
+
{t('detailPanel.disabled', { ns: 'plugin' })}
diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx index 357e714ba2..366139d12d 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx @@ -9,8 +9,8 @@ import * as React from 'react' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' -import Toast from '@/app/components/base/toast' import Tooltip from '@/app/components/base/tooltip' +import { toast } from '@/app/components/base/ui/toast' import { toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import { useDocLink } from '@/context/i18n' import { @@ -50,7 +50,7 @@ const EndpointList = ({ detail }: Props) => { hideEndpointModal() }, onError: () => { - Toast.notify({ type: 'error', message: t('actionMsg.modifiedUnsuccessfully', { ns: 'common' }) }) + toast.error(t('actionMsg.modifiedUnsuccessfully', { ns: 'common' })) }, }) @@ -64,7 +64,7 @@ const EndpointList = ({ detail }: Props) => { return (
-
+
{t('detailPanel.endpoints', { ns: 'plugin' })} {
-
{t('detailPanel.endpointsTip', { ns: 'plugin' })}
+
{t('detailPanel.endpointsTip', { ns: 'plugin' })}
-
+
{t('detailPanel.endpointsDocLink', { ns: 'plugin' })}
@@ -95,7 +95,7 @@ const EndpointList = ({ detail }: Props) => {
{data.endpoints.length === 0 && ( -
{t('detailPanel.endpointsEmpty', { ns: 'plugin' })}
+
{t('detailPanel.endpointsEmpty', { ns: 'plugin' })}
)}
{data.endpoints.map((item, index) => ( diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx index 929e990f90..4d93e14c8b 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx @@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Button from '@/app/components/base/button' import Drawer from '@/app/components/base/drawer' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' import { useRenderI18nObject } from '@/hooks/use-i18n' import { cn } from '@/utils/classnames' @@ -48,7 +48,10 @@ const EndpointModal: FC = ({ const handleSave = () => { for (const field of formSchemas) { if (field.required && !tempCredential[field.name]) { - Toast.notify({ type: 'error', message: t('errorMsg.fieldRequired', { ns: 'common', field: typeof field.label === 'string' ? field.label : getValueFromI18nObject(field.label as Record) }) }) + toast.error(t('errorMsg.fieldRequired', { + ns: 'common', + field: typeof field.label === 'string' ? field.label : getValueFromI18nObject(field.label as Record), + })) return } } @@ -83,12 +86,12 @@ const EndpointModal: FC = ({ <>
-
{t('detailPanel.endpointModalTitle', { ns: 'plugin' })}
+
{t('detailPanel.endpointModalTitle', { ns: 'plugin' })}
-
{t('detailPanel.endpointModalDesc', { ns: 'plugin' })}
+
{t('detailPanel.endpointModalDesc', { ns: 'plugin' })}
@@ -109,7 +112,7 @@ const EndpointModal: FC = ({ href={item.url} target="_blank" rel="noopener noreferrer" - className="body-xs-regular inline-flex items-center text-text-accent-secondary" + className="inline-flex items-center text-text-accent-secondary body-xs-regular" > {t('howToGet', { ns: 'tools' })} diff --git a/web/app/components/plugins/plugin-detail-panel/model-selector/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-detail-panel/model-selector/__tests__/index.spec.tsx index 9b04a710e0..107d42ada2 100644 --- a/web/app/components/plugins/plugin-detail-panel/model-selector/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/model-selector/__tests__/index.spec.tsx @@ -1,14 +1,29 @@ import type { Model, ModelItem } from '@/app/components/header/account-setting/model-provider-page/declarations' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -// Import component after mocks -import Toast from '@/app/components/base/toast' - import { ConfigurationMethodEnum, ModelStatusEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' + +// Import component after mocks import ModelParameterModal from '../index' // ==================== Mock Setup ==================== +const mockToastNotify = vi.fn() +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign( + (message: string, options?: { type?: string }) => mockToastNotify({ type: options?.type, message }), + { + success: (message: string) => mockToastNotify({ type: 'success', message }), + error: (message: string) => mockToastNotify({ type: 'error', message }), + warning: (message: string) => mockToastNotify({ type: 'warning', message }), + info: (message: string) => mockToastNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }, + ), +})) + // Mock provider context const mockProviderContextValue = { isAPIKeySet: true, @@ -53,8 +68,6 @@ vi.mock('@/utils/completion-params', () => ({ fetchAndMergeValidCompletionParams: (...args: unknown[]) => mockFetchAndMergeValidCompletionParams(...args), })) -const mockToastNotify = vi.spyOn(Toast, 'notify') - // Mock child components vi.mock('@/app/components/header/account-setting/model-provider-page/model-selector', () => ({ default: ({ defaultModel, modelList, scopeFeatures, onSelect }: { @@ -244,7 +257,6 @@ const setupModelLists = (config: { describe('ModelParameterModal', () => { beforeEach(() => { vi.clearAllMocks() - mockToastNotify.mockReturnValue({}) mockProviderContextValue.isAPIKeySet = true mockProviderContextValue.modelProviders = [] setupModelLists() @@ -865,9 +877,7 @@ describe('ModelParameterModal', () => { // Assert await waitFor(() => { - expect(Toast.notify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'warning' }), - ) + expect(mockToastNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'warning' })) }) }) @@ -892,9 +902,7 @@ describe('ModelParameterModal', () => { // Assert await waitFor(() => { - expect(Toast.notify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'error' }), - ) + expect(mockToastNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) }) }) }) diff --git a/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx index 04b78f98b7..6838bcec43 100644 --- a/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx @@ -10,12 +10,12 @@ import type { import type { TriggerProps } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal/trigger' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' import { Popover, PopoverContent, PopoverTrigger, } from '@/app/components/base/ui/popover' +import { toast } from '@/app/components/base/ui/toast' import { ModelStatusEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useModelList, @@ -134,14 +134,11 @@ const ModelParameterModal: FC = ({ const keys = Object.keys(removedDetails || {}) if (keys.length) { - Toast.notify({ - type: 'warning', - message: `${t('modelProvider.parametersInvalidRemoved', { ns: 'common' })}: ${keys.map(k => `${k} (${removedDetails[k]})`).join(', ')}`, - }) + toast.warning(`${t('modelProvider.parametersInvalidRemoved', { ns: 'common' })}: ${keys.map(k => `${k} (${removedDetails[k]})`).join(', ')}`) } } catch { - Toast.notify({ type: 'error', message: t('error', { ns: 'common' }) }) + toast.error(t('error', { ns: 'common' })) } } diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/log-viewer.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/log-viewer.spec.tsx index c6fb42faab..351c1f9d2d 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/log-viewer.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/log-viewer.spec.tsx @@ -1,12 +1,26 @@ import type { TriggerLogEntity } from '@/app/components/workflow/block-selector/types' import { cleanup, fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import Toast from '@/app/components/base/toast' import LogViewer from '../log-viewer' const mockToastNotify = vi.fn() const mockWriteText = vi.fn() +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign( + (message: string, options?: { type?: string }) => mockToastNotify({ type: options?.type, message }), + { + success: (message: string) => mockToastNotify({ type: 'success', message }), + error: (message: string) => mockToastNotify({ type: 'error', message }), + warning: (message: string) => mockToastNotify({ type: 'warning', message }), + info: (message: string) => mockToastNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }, + ), +})) + vi.mock('@/app/components/workflow/nodes/_base/components/editor/code-editor', () => ({ default: ({ value }: { value: unknown }) => (
{JSON.stringify(value)}
@@ -57,10 +71,6 @@ beforeEach(() => { }, configurable: true, }) - vi.spyOn(Toast, 'notify').mockImplementation((args) => { - mockToastNotify(args) - return { clear: vi.fn() } - }) }) describe('LogViewer', () => { diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/selector-entry.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/selector-entry.spec.tsx index d8d41ff9b2..3c4ff83fc8 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/selector-entry.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/selector-entry.spec.tsx @@ -26,10 +26,16 @@ vi.mock('@/service/use-triggers', () => ({ useDeleteTriggerSubscription: () => ({ mutate: vi.fn(), isPending: false }), })) -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: vi.fn(), - }, +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign(vi.fn(), { + success: vi.fn(), + error: vi.fn(), + warning: vi.fn(), + info: vi.fn(), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), })) const createSubscription = (overrides: Partial = {}): TriggerSubscription => ({ diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/selector-view.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/selector-view.spec.tsx index 83d0cdd89d..44cec53e28 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/selector-view.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/selector-view.spec.tsx @@ -1,7 +1,6 @@ import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types' import { fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import Toast from '@/app/components/base/toast' import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types' import { SubscriptionSelectorView } from '../selector-view' @@ -26,6 +25,18 @@ vi.mock('@/service/use-triggers', () => ({ useDeleteTriggerSubscription: () => ({ mutate: mockDelete, isPending: false }), })) +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign(vi.fn(), { + success: vi.fn(), + error: vi.fn(), + warning: vi.fn(), + info: vi.fn(), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), +})) + const createSubscription = (overrides: Partial = {}): TriggerSubscription => ({ id: 'sub-1', name: 'Subscription One', @@ -42,7 +53,6 @@ const createSubscription = (overrides: Partial = {}): Trigg beforeEach(() => { vi.clearAllMocks() mockSubscriptions = [createSubscription()] - vi.spyOn(Toast, 'notify').mockImplementation(() => ({ clear: vi.fn() })) }) describe('SubscriptionSelectorView', () => { diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/subscription-card.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/subscription-card.spec.tsx index a51bc2954f..4665c921ca 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/subscription-card.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/__tests__/subscription-card.spec.tsx @@ -1,7 +1,6 @@ import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types' import { fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import Toast from '@/app/components/base/toast' import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types' import SubscriptionCard from '../subscription-card' @@ -30,6 +29,18 @@ vi.mock('@/service/use-triggers', () => ({ useDeleteTriggerSubscription: () => ({ mutate: vi.fn(), isPending: false }), })) +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign(vi.fn(), { + success: vi.fn(), + error: vi.fn(), + warning: vi.fn(), + info: vi.fn(), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), +})) + const createSubscription = (overrides: Partial = {}): TriggerSubscription => ({ id: 'sub-1', name: 'Subscription One', @@ -45,7 +56,6 @@ const createSubscription = (overrides: Partial = {}): Trigg beforeEach(() => { vi.clearAllMocks() - vi.spyOn(Toast, 'notify').mockImplementation(() => ({ clear: vi.fn() })) }) describe('SubscriptionCard', () => { diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/common-modal.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/common-modal.spec.tsx index 21a4c3defa..72532ea38d 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/common-modal.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/common-modal.spec.tsx @@ -122,10 +122,16 @@ vi.mock('@/utils/urlValidation', () => ({ })) const mockToastNotify = vi.fn() -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: (params: unknown) => mockToastNotify(params), - }, +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign((params: unknown) => mockToastNotify(params), { + success: (message: unknown) => mockToastNotify({ type: 'success', message }), + error: (message: unknown) => mockToastNotify({ type: 'error', message }), + warning: (message: unknown) => mockToastNotify({ type: 'warning', message }), + info: (message: unknown) => mockToastNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), })) vi.mock('@/app/components/base/modal/modal', () => ({ diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/index.spec.tsx index 3fe9884b92..a36c108160 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/index.spec.tsx @@ -2,6 +2,7 @@ import type { SimpleDetail } from '../../../store' import type { TriggerOAuthConfig, TriggerProviderApiEntity, TriggerSubscription, TriggerSubscriptionBuilder } from '@/app/components/workflow/block-selector/types' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { toast } from '@/app/components/base/ui/toast' import { SupportedCreationMethods } from '@/app/components/plugins/types' import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types' import { CreateButtonType, CreateSubscriptionButton, DEFAULT_METHOD } from '../index' @@ -33,10 +34,16 @@ vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ }, })) -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: vi.fn(), - }, +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign(vi.fn(), { + success: vi.fn(), + error: vi.fn(), + warning: vi.fn(), + info: vi.fn(), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), })) let mockStoreDetail: SimpleDetail | undefined @@ -908,8 +915,6 @@ describe('CreateSubscriptionButton', () => { it('should handle OAuth initiation error', async () => { // Arrange - const Toast = await import('@/app/components/base/toast') - mockInitiateOAuth.mockImplementation((_provider: string, callbacks: { onError: () => void }) => { callbacks.onError() }) @@ -932,9 +937,7 @@ describe('CreateSubscriptionButton', () => { // Assert await waitFor(() => { - expect(Toast.default.notify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'error' }), - ) + expect(toast.error).toHaveBeenCalled() }) }) }) diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/oauth-client.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/oauth-client.spec.tsx index 12419a9bf3..ce53bf5b9a 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/oauth-client.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/__tests__/oauth-client.spec.tsx @@ -86,10 +86,19 @@ vi.mock('@/hooks/use-oauth', () => ({ })) const mockToastNotify = vi.fn() -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: (params: unknown) => mockToastNotify(params), - }, +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign( + (message: string, options?: { type?: string }) => mockToastNotify({ type: options?.type, message }), + { + success: (message: string) => mockToastNotify({ type: 'success', message }), + error: (message: string) => mockToastNotify({ type: 'error', message }), + warning: (message: string) => mockToastNotify({ type: 'warning', message }), + info: (message: string) => mockToastNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }, + ), })) const mockClipboardWriteText = vi.fn() diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/__tests__/use-oauth-client-state.spec.ts b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/__tests__/use-oauth-client-state.spec.ts index 89566f3af7..68864b0b80 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/__tests__/use-oauth-client-state.spec.ts +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/__tests__/use-oauth-client-state.spec.ts @@ -77,10 +77,19 @@ vi.mock('@/hooks/use-oauth', () => ({ })) const mockToastNotify = vi.fn() -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: (params: unknown) => mockToastNotify(params), - }, +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign( + (message: string, options?: { type?: string }) => mockToastNotify({ type: options?.type, message }), + { + success: (message: string) => mockToastNotify({ type: 'success', message }), + error: (message: string) => mockToastNotify({ type: 'error', message }), + warning: (message: string) => mockToastNotify({ type: 'warning', message }), + info: (message: string) => mockToastNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }, + ), })) // ============================================================================ diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/use-common-modal-state.ts b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/use-common-modal-state.ts index b01312d3d1..99c42f6fc5 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/use-common-modal-state.ts +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/use-common-modal-state.ts @@ -7,7 +7,7 @@ import type { BuildTriggerSubscriptionPayload } from '@/service/use-triggers' import { debounce } from 'es-toolkit/compat' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { SupportedCreationMethods } from '@/app/components/plugins/types' import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types' import { @@ -154,10 +154,7 @@ export const useCommonModalState = ({ onError: async (error: unknown) => { const errorMessage = await parsePluginErrorMessage(error) || t('modal.errors.updateFailed', { ns: 'pluginTrigger' }) console.error('Failed to update subscription builder:', error) - Toast.notify({ - type: 'error', - message: errorMessage, - }) + toast.error(errorMessage) }, }, ) @@ -178,10 +175,7 @@ export const useCommonModalState = ({ } catch (error) { console.error('createBuilder error:', error) - Toast.notify({ - type: 'error', - message: t('modal.errors.createFailed', { ns: 'pluginTrigger' }), - }) + toast.error(t('modal.errors.createFailed', { ns: 'pluginTrigger' })) } } if (!isInitializedRef.current && !subscriptionBuilder && detail?.provider) @@ -239,10 +233,7 @@ export const useCommonModalState = ({ const handleVerify = useCallback(() => { // Guard against uninitialized state if (!detail?.provider || !subscriptionBuilder?.id) { - Toast.notify({ - type: 'error', - message: 'Subscription builder not initialized', - }) + toast.error('Subscription builder not initialized') return } @@ -250,10 +241,7 @@ export const useCommonModalState = ({ const credentials = apiKeyCredentialsFormValues.values if (!Object.keys(credentials).length) { - Toast.notify({ - type: 'error', - message: 'Please fill in all required credentials', - }) + toast.error('Please fill in all required credentials') return } @@ -270,10 +258,7 @@ export const useCommonModalState = ({ }, { onSuccess: () => { - Toast.notify({ - type: 'success', - message: t('modal.apiKey.verify.success', { ns: 'pluginTrigger' }), - }) + toast.success(t('modal.apiKey.verify.success', { ns: 'pluginTrigger' })) setCurrentStep(ApiKeyStep.Configuration) }, onError: async (error: unknown) => { @@ -290,10 +275,7 @@ export const useCommonModalState = ({ // Handle create const handleCreate = useCallback(() => { if (!subscriptionBuilder) { - Toast.notify({ - type: 'error', - message: 'Subscription builder not found', - }) + toast.error('Subscription builder not found') return } @@ -327,19 +309,13 @@ export const useCommonModalState = ({ params, { onSuccess: () => { - Toast.notify({ - type: 'success', - message: t('subscription.createSuccess', { ns: 'pluginTrigger' }), - }) + toast.success(t('subscription.createSuccess', { ns: 'pluginTrigger' })) onClose() refetch?.() }, onError: async (error: unknown) => { const errorMessage = await parsePluginErrorMessage(error) || t('subscription.createFailed', { ns: 'pluginTrigger' }) - Toast.notify({ - type: 'error', - message: errorMessage, - }) + toast.error(errorMessage) }, }, ) diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/use-oauth-client-state.ts b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/use-oauth-client-state.ts index 6a551051e2..e5a5ded9df 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/use-oauth-client-state.ts +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/use-oauth-client-state.ts @@ -4,7 +4,7 @@ import type { TriggerOAuthClientParams, TriggerOAuthConfig, TriggerSubscriptionB import type { ConfigureTriggerOAuthPayload } from '@/service/use-triggers' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { openOAuthPopup } from '@/hooks/use-oauth' import { useConfigureTriggerOAuth, @@ -118,20 +118,14 @@ export const useOAuthClientState = ({ openOAuthPopup(response.authorization_url, (callbackData) => { if (!callbackData) return - Toast.notify({ - type: 'success', - message: t('modal.oauth.authorization.authSuccess', { ns: 'pluginTrigger' }), - }) + toast.success(t('modal.oauth.authorization.authSuccess', { ns: 'pluginTrigger' })) onClose() showOAuthCreateModal(response.subscription_builder) }) }, onError: () => { setAuthorizationStatus(AuthorizationStatusEnum.Failed) - Toast.notify({ - type: 'error', - message: t('modal.oauth.authorization.authFailed', { ns: 'pluginTrigger' }), - }) + toast.error(t('modal.oauth.authorization.authFailed', { ns: 'pluginTrigger' })) }, }) }, [providerName, initiateOAuth, onClose, showOAuthCreateModal, t]) @@ -141,16 +135,10 @@ export const useOAuthClientState = ({ deleteOAuth(providerName, { onSuccess: () => { onClose() - Toast.notify({ - type: 'success', - message: t('modal.oauth.remove.success', { ns: 'pluginTrigger' }), - }) + toast.success(t('modal.oauth.remove.success', { ns: 'pluginTrigger' })) }, onError: (error: unknown) => { - Toast.notify({ - type: 'error', - message: getErrorMessage(error, t('modal.oauth.remove.failed', { ns: 'pluginTrigger' })), - }) + toast.error(getErrorMessage(error, t('modal.oauth.remove.failed', { ns: 'pluginTrigger' }))) }, }) }, [providerName, deleteOAuth, onClose, t]) @@ -187,10 +175,7 @@ export const useOAuthClientState = ({ return } onClose() - Toast.notify({ - type: 'success', - message: t('modal.oauth.save.success', { ns: 'pluginTrigger' }), - }) + toast.success(t('modal.oauth.save.success', { ns: 'pluginTrigger' })) }, }) }, [clientType, providerName, oauthClientSchema, oauthConfig?.params, configureOAuth, handleAuthorization, onClose, t]) diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx index eecaf165fb..bd0846c15e 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx @@ -8,8 +8,8 @@ import { ActionButton, ActionButtonState } from '@/app/components/base/action-bu import Badge from '@/app/components/base/badge' import { Button } from '@/app/components/base/button' import CustomSelect from '@/app/components/base/select/custom' -import Toast from '@/app/components/base/toast' import Tooltip from '@/app/components/base/tooltip' +import { toast } from '@/app/components/base/ui/toast' import { openOAuthPopup } from '@/hooks/use-oauth' import { useInitiateTriggerOAuth, useTriggerOAuthConfig, useTriggerProviderInfo } from '@/service/use-triggers' import { cn } from '@/utils/classnames' @@ -107,19 +107,13 @@ export const CreateSubscriptionButton = ({ buttonType = CreateButtonType.FULL_BU onSuccess: (response) => { openOAuthPopup(response.authorization_url, (callbackData) => { if (callbackData) { - Toast.notify({ - type: 'success', - message: t('modal.oauth.authorization.authSuccess', { ns: 'pluginTrigger' }), - }) + toast.success(t('modal.oauth.authorization.authSuccess', { ns: 'pluginTrigger' })) setSelectedCreateInfo({ type: SupportedCreationMethods.OAUTH, builder: response.subscription_builder }) } }) }, onError: () => { - Toast.notify({ - type: 'error', - message: t('modal.oauth.authorization.authFailed', { ns: 'pluginTrigger' }), - }) + toast.error(t('modal.oauth.authorization.authFailed', { ns: 'pluginTrigger' })) }, }) } diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx index b7f9b8ebec..d4bc92169c 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx @@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { BaseForm } from '@/app/components/base/form/components/base' import Modal from '@/app/components/base/modal/modal' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card' import { usePluginStore } from '../../store' import { ClientTypeEnum, useOAuthClientState } from './hooks/use-oauth-client-state' @@ -48,10 +48,7 @@ export const OAuthClientSettingsModal = ({ oauthConfig, onClose, showOAuthCreate const handleCopyRedirectUri = () => { navigator.clipboard.writeText(oauthConfig?.redirect_uri || '') - Toast.notify({ - type: 'success', - message: t('actionMsg.copySuccessfully', { ns: 'common' }), - }) + toast.success(t('actionMsg.copySuccessfully', { ns: 'common' })) } return ( diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/apikey-edit-modal.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/apikey-edit-modal.spec.tsx index e0fb7455ce..7f22a06295 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/apikey-edit-modal.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/apikey-edit-modal.spec.tsx @@ -47,13 +47,19 @@ vi.mock('@/service/use-triggers', () => ({ useTriggerPluginDynamicOptions: () => ({ data: [], isLoading: false }), })) -vi.mock('@/app/components/base/toast', async (importOriginal) => { - const actual = await importOriginal() +vi.mock('@/app/components/base/ui/toast', async (importOriginal) => { + const actual = await importOriginal() return { ...actual, - default: { - notify: (args: { type: string, message: string }) => mockToast(args), - }, + toast: Object.assign((args: { type: string, message: string }) => mockToast(args), { + success: (message: string) => mockToast({ type: 'success', message }), + error: (message: string) => mockToast({ type: 'error', message }), + warning: (message: string) => mockToast({ type: 'warning', message }), + info: (message: string) => mockToast({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), } }) diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/index.spec.tsx index 7d188a3f6d..126d8e366d 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/index.spec.tsx @@ -13,8 +13,16 @@ import { OAuthEditModal } from '../oauth-edit-modal' // ==================== Mock Setup ==================== const mockToastNotify = vi.fn() -vi.mock('@/app/components/base/toast', () => ({ - default: { notify: (params: unknown) => mockToastNotify(params) }, +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign((message: string, options?: { type?: string }) => mockToastNotify({ type: options?.type, message }), { + success: (message: string) => mockToastNotify({ type: 'success', message }), + error: (message: string) => mockToastNotify({ type: 'error', message }), + warning: (message: string) => mockToastNotify({ type: 'warning', message }), + info: (message: string) => mockToastNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), })) const mockParsePluginErrorMessage = vi.fn() diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/manual-edit-modal.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/manual-edit-modal.spec.tsx index 60a8428287..52572b3560 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/manual-edit-modal.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/manual-edit-modal.spec.tsx @@ -30,13 +30,19 @@ vi.mock('@/service/use-triggers', () => ({ useTriggerPluginDynamicOptions: () => ({ data: [], isLoading: false }), })) -vi.mock('@/app/components/base/toast', async (importOriginal) => { - const actual = await importOriginal() +vi.mock('@/app/components/base/ui/toast', async (importOriginal) => { + const actual = await importOriginal() return { ...actual, - default: { - notify: (args: { type: string, message: string }) => mockToast(args), - }, + toast: Object.assign((args: { type: string, message: string }) => mockToast(args), { + success: (message: string) => mockToast({ type: 'success', message }), + error: (message: string) => mockToast({ type: 'error', message }), + warning: (message: string) => mockToast({ type: 'warning', message }), + info: (message: string) => mockToast({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), } }) diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/oauth-edit-modal.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/oauth-edit-modal.spec.tsx index 8835b46695..95b2cca6af 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/oauth-edit-modal.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/__tests__/oauth-edit-modal.spec.tsx @@ -30,13 +30,19 @@ vi.mock('@/service/use-triggers', () => ({ useTriggerPluginDynamicOptions: () => ({ data: [], isLoading: false }), })) -vi.mock('@/app/components/base/toast', async (importOriginal) => { - const actual = await importOriginal() +vi.mock('@/app/components/base/ui/toast', async (importOriginal) => { + const actual = await importOriginal() return { ...actual, - default: { - notify: (args: { type: string, message: string }) => mockToast(args), - }, + toast: Object.assign((args: { type: string, message: string }) => mockToast(args), { + success: (message: string) => mockToast({ type: 'success', message }), + error: (message: string) => mockToast({ type: 'error', message }), + warning: (message: string) => mockToast({ type: 'warning', message }), + info: (message: string) => mockToast({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), } }) diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx index a4093ed00b..247beaa626 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx @@ -9,7 +9,7 @@ import { EncryptedBottom } from '@/app/components/base/encrypted-bottom' import { BaseForm } from '@/app/components/base/form/components/base' import { FormTypeEnum } from '@/app/components/base/form/types' import Modal from '@/app/components/base/modal/modal' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance' import { useUpdateTriggerSubscription, useVerifyTriggerSubscription } from '@/service/use-triggers' import { parsePluginErrorMessage } from '@/utils/error-parser' @@ -65,7 +65,7 @@ const StatusStep = ({ isActive, text, onClick, clickable }: { }) => { return (
{ - Toast.notify({ - type: 'success', - message: t('modal.apiKey.verify.success', { ns: 'pluginTrigger' }), - }) + toast.success(t('modal.apiKey.verify.success', { ns: 'pluginTrigger' })) // Only save credentials if any field was modified (not all hidden) setVerifiedCredentials(areAllCredentialsHidden(credentials) ? null : credentials) setCurrentStep(EditStep.EditConfiguration) }, onError: async (error: unknown) => { const errorMessage = await parsePluginErrorMessage(error) || t('modal.apiKey.verify.error', { ns: 'pluginTrigger' }) - Toast.notify({ - type: 'error', - message: errorMessage, - }) + toast.error(errorMessage) }, }, ) @@ -192,19 +186,13 @@ export const ApiKeyEditModal = ({ onClose, subscription, pluginDetail }: Props) }, { onSuccess: () => { - Toast.notify({ - type: 'success', - message: t('subscription.list.item.actions.edit.success', { ns: 'pluginTrigger' }), - }) + toast.success(t('subscription.list.item.actions.edit.success', { ns: 'pluginTrigger' })) refetch?.() onClose() }, onError: async (error: unknown) => { const errorMessage = await parsePluginErrorMessage(error) || t('subscription.list.item.actions.edit.error', { ns: 'pluginTrigger' }) - Toast.notify({ - type: 'error', - message: errorMessage, - }) + toast.error(errorMessage) }, }, ) diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx index 262235e6ed..e1741da8e7 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx @@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next' import { BaseForm } from '@/app/components/base/form/components/base' import { FormTypeEnum } from '@/app/components/base/form/types' import Modal from '@/app/components/base/modal/modal' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance' import { useUpdateTriggerSubscription } from '@/service/use-triggers' import { ReadmeShowType } from '../../../readme-panel/store' @@ -94,18 +94,12 @@ export const ManualEditModal = ({ onClose, subscription, pluginDetail }: Props) }, { onSuccess: () => { - Toast.notify({ - type: 'success', - message: t('subscription.list.item.actions.edit.success', { ns: 'pluginTrigger' }), - }) + toast.success(t('subscription.list.item.actions.edit.success', { ns: 'pluginTrigger' })) refetch?.() onClose() }, onError: (error: unknown) => { - Toast.notify({ - type: 'error', - message: getErrorMessage(error, t('subscription.list.item.actions.edit.error', { ns: 'pluginTrigger' })), - }) + toast.error(getErrorMessage(error, t('subscription.list.item.actions.edit.error', { ns: 'pluginTrigger' }))) }, }, ) diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx index e57b9c0151..c43a00a322 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx @@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next' import { BaseForm } from '@/app/components/base/form/components/base' import { FormTypeEnum } from '@/app/components/base/form/types' import Modal from '@/app/components/base/modal/modal' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance' import { useUpdateTriggerSubscription } from '@/service/use-triggers' import { ReadmeShowType } from '../../../readme-panel/store' @@ -94,18 +94,12 @@ export const OAuthEditModal = ({ onClose, subscription, pluginDetail }: Props) = }, { onSuccess: () => { - Toast.notify({ - type: 'success', - message: t('subscription.list.item.actions.edit.success', { ns: 'pluginTrigger' }), - }) + toast.success(t('subscription.list.item.actions.edit.success', { ns: 'pluginTrigger' })) refetch?.() onClose() }, onError: (error: unknown) => { - Toast.notify({ - type: 'error', - message: getErrorMessage(error, t('subscription.list.item.actions.edit.error', { ns: 'pluginTrigger' })), - }) + toast.error(getErrorMessage(error, t('subscription.list.item.actions.edit.error', { ns: 'pluginTrigger' }))) }, }, ) diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx index 3b4edd1b85..6ccab000c4 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx @@ -11,7 +11,7 @@ import dayjs from 'dayjs' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { cn } from '@/utils/classnames' @@ -89,10 +89,7 @@ const LogViewer = ({ logs, className }: Props) => { onClick={(e) => { e.stopPropagation() navigator.clipboard.writeText(String(parsedData)) - Toast.notify({ - type: 'success', - message: t('actionMsg.copySuccessfully', { ns: 'common' }), - }) + toast.success(t('actionMsg.copySuccessfully', { ns: 'common' })) }} className="rounded-md p-0.5 hover:bg-components-panel-border" > diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/__tests__/index.spec.tsx index 26e4de0fd7..537e99d733 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/__tests__/index.spec.tsx @@ -298,8 +298,16 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/model-modal // Mock Toast - need to track notify calls for assertions const mockToastNotify = vi.fn() -vi.mock('@/app/components/base/toast', () => ({ - default: { notify: (...args: unknown[]) => mockToastNotify(...args) }, +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign((message: string, options?: { type?: string }) => mockToastNotify({ type: options?.type, message }), { + success: (message: string) => mockToastNotify({ type: 'success', message }), + error: (message: string) => mockToastNotify({ type: 'error', message }), + warning: (message: string) => mockToastNotify({ type: 'warning', message }), + info: (message: string) => mockToastNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), })) // ==================== Test Utilities ==================== @@ -1943,7 +1951,7 @@ describe('ToolCredentialsForm Component', () => { const saveBtn = screen.getByText(/save/i) fireEvent.click(saveBtn) - // Toast.notify should have been called with error (lines 49-50) + // notifyToast should have been called with error (lines 49-50) expect(mockToastNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })) // onSaved should not be called because validation fails expect(onSaved).not.toHaveBeenCalled() diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/components/__tests__/tool-credentials-form.spec.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/components/__tests__/tool-credentials-form.spec.tsx index cb5b929d29..e1b8ca86fe 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/components/__tests__/tool-credentials-form.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/components/__tests__/tool-credentials-form.spec.tsx @@ -10,12 +10,16 @@ vi.mock('@/utils/classnames', () => ({ cn: (...args: unknown[]) => args.filter(Boolean).join(' '), })) -vi.mock('@/app/components/base/toast', () => ({ - default: { notify: vi.fn() }, -})) - -vi.mock('@/app/components/base/toast/context', () => ({ - useToastContext: () => ({ notify: vi.fn() }), +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign(vi.fn(), { + success: vi.fn(), + error: vi.fn(), + warning: vi.fn(), + info: vi.fn(), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }), })) const mockFormSchemas = [ diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/components/tool-credentials-form.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/components/tool-credentials-form.tsx index 0207f65336..f4d39c9ec1 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/components/tool-credentials-form.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/components/tool-credentials-form.tsx @@ -10,7 +10,7 @@ import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Loading from '@/app/components/base/loading' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import { useRenderI18nObject } from '@/hooks/use-i18n' @@ -49,7 +49,10 @@ const ToolCredentialForm: FC = ({ return for (const field of credentialSchema) { if (field.required && !tempCredential[field.name]) { - Toast.notify({ type: 'error', message: t('errorMsg.fieldRequired', { ns: 'common', field: getValueFromI18nObject(field.label) }) }) + toast.error(t('errorMsg.fieldRequired', { + ns: 'common', + field: getValueFromI18nObject(field.label), + })) return } } diff --git a/web/app/components/plugins/plugin-item/__tests__/action.spec.tsx b/web/app/components/plugins/plugin-item/__tests__/action.spec.tsx index 8467c983d8..b4d21c9403 100644 --- a/web/app/components/plugins/plugin-item/__tests__/action.spec.tsx +++ b/web/app/components/plugins/plugin-item/__tests__/action.spec.tsx @@ -1,7 +1,6 @@ import type { MetaData, PluginCategoryEnum } from '../../types' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import Toast from '@/app/components/base/toast' // ==================== Imports (after mocks) ==================== @@ -17,12 +16,29 @@ const { mockCheckForUpdates, mockSetShowUpdatePluginModal, mockInvalidateInstalledPluginList, + mockToastNotify, } = vi.hoisted(() => ({ mockUninstallPlugin: vi.fn(), mockFetchReleases: vi.fn(), mockCheckForUpdates: vi.fn(), mockSetShowUpdatePluginModal: vi.fn(), mockInvalidateInstalledPluginList: vi.fn(), + mockToastNotify: vi.fn(), +})) + +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: Object.assign( + (message: string, options?: { type?: string }) => mockToastNotify({ type: options?.type, message }), + { + success: (message: string) => mockToastNotify({ type: 'success', message }), + error: (message: string) => mockToastNotify({ type: 'error', message }), + warning: (message: string) => mockToastNotify({ type: 'warning', message }), + info: (message: string) => mockToastNotify({ type: 'info', message }), + dismiss: vi.fn(), + update: vi.fn(), + promise: vi.fn(), + }, + ), })) // Mock uninstall plugin service @@ -140,13 +156,8 @@ const getActionButtons = () => screen.getAllByRole('button') const queryActionButtons = () => screen.queryAllByRole('button') describe('Action Component', () => { - // Spy on Toast.notify - real component but we track calls - let toastNotifySpy: ReturnType - beforeEach(() => { vi.clearAllMocks() - // Spy on Toast.notify and mock implementation to avoid DOM side effects - toastNotifySpy = vi.spyOn(Toast, 'notify').mockImplementation(() => ({ clear: vi.fn() })) mockUninstallPlugin.mockResolvedValue({ success: true }) mockFetchReleases.mockResolvedValue([]) mockCheckForUpdates.mockReturnValue({ @@ -155,10 +166,6 @@ describe('Action Component', () => { }) }) - afterEach(() => { - toastNotifySpy.mockRestore() - }) - // ==================== Rendering Tests ==================== describe('Rendering', () => { it('should render delete button when isShowDelete is true', () => { @@ -563,9 +570,9 @@ describe('Action Component', () => { render() fireEvent.click(getActionButtons()[0]) - // Assert - Toast.notify is called with the toast props + // Assert - toast is called with the translated payload await waitFor(() => { - expect(toastNotifySpy).toHaveBeenCalledWith({ type: 'success', message: 'Already up to date' }) + expect(mockToastNotify).toHaveBeenCalledWith({ type: 'success', message: 'Already up to date' }) }) }) diff --git a/web/app/components/plugins/plugin-item/action.tsx b/web/app/components/plugins/plugin-item/action.tsx index 171e54acab..413b41e895 100644 --- a/web/app/components/plugins/plugin-item/action.tsx +++ b/web/app/components/plugins/plugin-item/action.tsx @@ -7,7 +7,7 @@ import { useBoolean } from 'ahooks' import * as React from 'react' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { useModalContext } from '@/context/modal-context' import { uninstallPlugin } from '@/service/plugins' import { useInvalidateInstalledPluginList } from '@/service/use-plugins' @@ -65,7 +65,7 @@ const Action: FC = ({ if (fetchedReleases.length === 0) return const { needUpdate, toastProps } = checkForUpdates(fetchedReleases, meta!.version) - Toast.notify(toastProps) + toast(toastProps.message, { type: toastProps.type }) if (needUpdate) { setShowUpdatePluginModal({ onSaveCallback: () => { diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 04e8be3afd..0f69e6ce33 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -5062,9 +5062,6 @@ } }, "app/components/plugins/install-plugin/hooks.ts": { - "no-restricted-imports": { - "count": 2 - }, "ts/no-explicit-any": { "count": 4 } @@ -5100,9 +5097,6 @@ }, "app/components/plugins/install-plugin/install-from-github/index.tsx": { "no-restricted-imports": { - "count": 3 - }, - "tailwindcss/enforce-consistent-class-order": { "count": 2 }, "ts/no-explicit-any": { @@ -5367,17 +5361,9 @@ "count": 1 } }, - "app/components/plugins/plugin-detail-panel/detail-header/hooks/use-plugin-operations.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "app/components/plugins/plugin-detail-panel/endpoint-card.tsx": { "no-restricted-imports": { - "count": 3 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 5 + "count": 2 }, "ts/no-explicit-any": { "count": 2 @@ -5385,22 +5371,13 @@ }, "app/components/plugins/plugin-detail-panel/endpoint-list.tsx": { "no-restricted-imports": { - "count": 2 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 4 + "count": 1 }, "ts/no-explicit-any": { "count": 2 } }, "app/components/plugins/plugin-detail-panel/endpoint-modal.tsx": { - "no-restricted-imports": { - "count": 1 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 3 - }, "ts/no-explicit-any": { "count": 7 } @@ -5414,9 +5391,6 @@ } }, "app/components/plugins/plugin-detail-panel/model-selector/index.tsx": { - "no-restricted-imports": { - "count": 1 - }, "ts/no-explicit-any": { "count": 3 } @@ -5471,27 +5445,21 @@ "app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/use-common-modal-state.ts": { "erasable-syntax-only/enums": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "app/components/plugins/plugin-detail-panel/subscription-list/create/hooks/use-oauth-client-state.ts": { "erasable-syntax-only/enums": { "count": 2 - }, - "no-restricted-imports": { - "count": 1 } }, "app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx": { "no-restricted-imports": { - "count": 4 + "count": 3 } }, "app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx": { "no-restricted-imports": { - "count": 2 + "count": 1 }, "tailwindcss/enforce-consistent-class-order": { "count": 3 @@ -5507,20 +5475,17 @@ "count": 1 }, "no-restricted-imports": { - "count": 2 - }, - "tailwindcss/enforce-consistent-class-order": { "count": 1 } }, "app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx": { "no-restricted-imports": { - "count": 2 + "count": 1 } }, "app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx": { "no-restricted-imports": { - "count": 2 + "count": 1 } }, "app/components/plugins/plugin-detail-panel/subscription-list/index.tsx": { @@ -5540,9 +5505,6 @@ "erasable-syntax-only/enums": { "count": 1 }, - "no-restricted-imports": { - "count": 1 - }, "tailwindcss/enforce-consistent-class-order": { "count": 5 }, @@ -5600,11 +5562,6 @@ "count": 2 } }, - "app/components/plugins/plugin-detail-panel/tool-selector/components/tool-credentials-form.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "app/components/plugins/plugin-detail-panel/tool-selector/components/tool-item.tsx": { "no-restricted-imports": { "count": 2 @@ -5643,7 +5600,7 @@ }, "app/components/plugins/plugin-item/action.tsx": { "no-restricted-imports": { - "count": 3 + "count": 2 } }, "app/components/plugins/plugin-item/index.tsx": {