From e3a08b40f235e9c31028911fb7be33ea7fc9dc61 Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Fri, 10 Jul 2026 22:08:33 +0800 Subject: [PATCH] test(web): centralize i18n mocks (#38670) --- .../app-sidebar/dataset-info-flow.test.tsx | 10 -- .../app/app-access-control-flow.test.tsx | 10 -- web/__tests__/app/app-publisher-flow.test.tsx | 32 ++-- .../billing/cloud-plan-payment-flow.test.tsx | 4 - .../education-verification-flow.test.tsx | 4 - .../billing/self-hosted-plan-flow.test.tsx | 4 - .../custom/custom-page-flow.test.tsx | 10 -- .../metadata-management-flow.test.tsx | 9 -- .../develop/develop-page-flow.test.tsx | 5 - .../explore/explore-app-list-flow.test.tsx | 6 +- .../search-error-handling.test.ts | 11 -- .../header/account-dropdown-flow.test.tsx | 8 +- .../plugins/plugin-card-rendering.test.tsx | 13 -- .../plugins/plugin-page-shell-flow.test.tsx | 14 -- .../rag-pipeline/test-run-flow.test.ts | 9 -- .../share/text-generation-mode-flow.test.tsx | 10 -- .../tools/provider-list-shell-flow.test.tsx | 10 -- .../app/annotation/__tests__/index.spec.tsx | 8 +- .../__tests__/csv-downloader.spec.tsx | 8 +- .../__tests__/features-wrapper.spec.tsx | 14 +- .../app-publisher/__tests__/index.spec.tsx | 72 ++++----- .../publish-with-multiple-model.spec.tsx | 18 +-- .../app-publisher/__tests__/sections.spec.tsx | 39 ++--- .../__tests__/version-info-modal.spec.tsx | 27 ++-- .../__tests__/advanced-prompt-input.spec.tsx | 14 +- .../__tests__/simple-prompt-input.spec.tsx | 20 +-- .../__tests__/index-logic.spec.tsx | 10 -- .../__tests__/setting-built-in-tool.spec.tsx | 4 - .../__tests__/automatic-btn.spec.tsx | 11 +- .../__tests__/get-automatic-res.spec.tsx | 28 ++-- .../automatic/__tests__/idea-output.spec.tsx | 19 +-- .../__tests__/instruction-editor.spec.tsx | 16 +- .../__tests__/prompt-res-in-workflow.spec.tsx | 12 +- .../automatic/__tests__/result.spec.tsx | 14 +- .../__tests__/version-selector.spec.tsx | 23 +-- .../__tests__/get-code-generator-res.spec.tsx | 26 +--- .../debug/__tests__/chat-user-input.spec.tsx | 14 +- .../__tests__/use-configuration.spec.tsx | 10 -- .../external-data-tool-modal.spec.tsx | 34 ++-- .../__tests__/dsl-confirm-modal.spec.tsx | 15 +- .../__tests__/index.spec.tsx | 56 +++---- .../__tests__/uploader.spec.tsx | 15 +- .../duplicate-modal/__tests__/index.spec.tsx | 20 +-- .../app/log/__tests__/filter.spec.tsx | 39 ++--- .../app/log/__tests__/index.spec.tsx | 24 +-- .../app/log/__tests__/list.spec.tsx | 14 +- .../app/log/__tests__/model-info.spec.tsx | 11 +- .../app/log/__tests__/var-panel.spec.tsx | 23 +-- .../__tests__/app-card-sections.spec.tsx | 20 +-- .../app/overview/__tests__/app-card.spec.tsx | 84 +++++----- .../app/overview/__tests__/app-chart.spec.tsx | 12 +- .../overview/__tests__/trigger-card.spec.tsx | 25 +-- .../item/__tests__/action-groups.spec.tsx | 32 ++-- .../item/__tests__/index.spec.tsx | 22 +-- .../item/__tests__/workflow-body.spec.tsx | 12 +- .../__tests__/iteration.spec.tsx | 2 +- .../answer/__tests__/reasoning-panel.spec.tsx | 20 +-- .../moderation/__tests__/index.spec.tsx | 5 - .../__tests__/think-block.spec.tsx | 32 ++-- .../__tests__/no-linked-apps-panel.spec.tsx | 3 +- .../file-uploader/__tests__/index.spec.tsx | 5 - .../hooks/__tests__/use-file-upload.spec.tsx | 7 - .../__tests__/indexing-mode-section.spec.tsx | 4 - .../__tests__/documents-header.spec.tsx | 3 - .../__tests__/use-local-file-upload.spec.tsx | 7 - .../__tests__/step-three-content.spec.tsx | 4 - .../__tests__/Form.spec.tsx | 3 +- .../__tests__/index.spec.tsx | 8 +- .../create/__tests__/InfoPanel.spec.tsx | 3 +- .../extra-info/__tests__/statistics.spec.tsx | 5 +- .../__tests__/modify-retrieval-modal.spec.tsx | 4 - .../settings/form/__tests__/index.spec.tsx | 4 - .../__tests__/indexing-section.spec.tsx | 47 ++---- .../explore/app-list/__tests__/index.spec.tsx | 6 +- .../explore/banner/__tests__/banner.spec.tsx | 6 +- .../sidebar/no-apps/__tests__/index.spec.tsx | 4 +- .../goto-anything/__tests__/index.spec.tsx | 5 - .../commands/__tests__/create.spec.tsx | 10 -- .../commands/__tests__/language.spec.ts | 10 -- .../commands/__tests__/refine.spec.tsx | 10 -- .../actions/commands/__tests__/slash.spec.tsx | 16 +- .../actions/commands/__tests__/theme.spec.ts | 10 -- .../use-goto-anything-results.spec.ts | 5 - .../account-dropdown/__tests__/index.spec.tsx | 4 - .../__tests__/plugin-actions.spec.tsx | 5 - .../invite-modal/__tests__/index.spec.tsx | 4 +- .../__tests__/hooks.spec.ts | 8 +- .../__tests__/provider-card-actions.spec.tsx | 5 - .../__tests__/tool-provider-card.spec.tsx | 7 +- .../main-nav/__tests__/layout.spec.tsx | 14 +- .../plugins/__tests__/provider-card.spec.tsx | 4 - .../sticky-search-and-switch-wrapper.spec.tsx | 9 -- .../description/__tests__/index.spec.tsx | 8 +- .../list/__tests__/card-wrapper.spec.tsx | 10 -- .../marketplace/list/__tests__/index.spec.tsx | 5 - .../__tests__/list-with-collection.spec.tsx | 11 -- .../__tests__/search-box-wrapper.spec.tsx | 10 -- .../trigger/__tests__/marketplace.spec.tsx | 9 -- .../__tests__/detail-header.spec.tsx | 6 - .../__tests__/endpoint-list.spec.tsx | 4 - .../detail-header/__tests__/index.spec.tsx | 6 - .../__tests__/header-modals.spec.tsx | 4 - .../plugin-page/__tests__/debug-info.spec.tsx | 8 +- .../__tests__/plugins-panel.spec.tsx | 5 - .../plugin-tasks/__tests__/index.spec.tsx | 5 - .../__tests__/plugin-task-list.spec.tsx | 5 - .../__tests__/index.spec.tsx | 3 - .../__tests__/tool-item.spec.tsx | 5 - .../update-plugin/__tests__/index.spec.tsx | 3 - .../hooks/__tests__/index.spec.ts | 4 - .../result/__tests__/index.spec.tsx | 4 +- .../__tests__/test-api.spec.tsx | 4 - .../marketplace/__tests__/index.spec.tsx | 2 +- .../tools/provider/__tests__/detail.spec.tsx | 4 - .../provider/__tests__/tool-item.spec.tsx | 5 - .../__tests__/featured-triggers.spec.tsx | 4 - .../workflow/comment/mention-input.spec.tsx | 10 -- .../workflow/comment/thread.spec.tsx | 10 -- .../__tests__/use-nodes-meta-data.spec.tsx | 8 +- .../__tests__/use-workflow-history.spec.tsx | 14 +- .../agent-strategy-selector.spec.tsx | 16 +- .../next-step/__tests__/operator.spec.tsx | 9 -- .../hooks/__tests__/use-one-step-run.spec.ts | 10 -- .../__tests__/single-run-form.spec.tsx | 11 +- .../__tests__/use-interactions.spec.tsx | 10 -- .../__tests__/json-importer.spec.tsx | 38 ++--- .../operator/__tests__/more-actions.spec.tsx | 10 -- .../__tests__/human-input-form-list.spec.tsx | 5 - .../comments-panel/__tests__/index.spec.tsx | 10 -- .../__tests__/search-input.spec.tsx | 16 +- web/app/install/installForm.spec.tsx | 2 +- web/app/signin/__tests__/_header.spec.tsx | 5 - .../agent-detail/logs/__tests__/page.spec.tsx | 4 - web/hooks/use-format-time-from-now.spec.ts | 3 +- .../__tests__/plural-selector.spec.ts | 4 +- web/service/__tests__/use-explore.spec.tsx | 4 - web/test/__tests__/i18n-mock.spec.ts | 31 +++- web/test/i18n-mock.ts | 145 ++++++++++++++---- 138 files changed, 519 insertions(+), 1332 deletions(-) diff --git a/web/__tests__/app-sidebar/dataset-info-flow.test.tsx b/web/__tests__/app-sidebar/dataset-info-flow.test.tsx index 6391aac3d87..b3cb9527bca 100644 --- a/web/__tests__/app-sidebar/dataset-info-flow.test.tsx +++ b/web/__tests__/app-sidebar/dataset-info-flow.test.tsx @@ -16,16 +16,6 @@ const mockDeleteDataset = vi.fn() const mockDownloadBlob = vi.fn() let mockDataset: DataSet - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - vi.mock('@/next/navigation', () => ({ useRouter: () => ({ replace: mockReplace, diff --git a/web/__tests__/app/app-access-control-flow.test.tsx b/web/__tests__/app/app-access-control-flow.test.tsx index 0f999e75f43..5db55c55835 100644 --- a/web/__tests__/app/app-access-control-flow.test.tsx +++ b/web/__tests__/app/app-access-control-flow.test.tsx @@ -32,16 +32,6 @@ const renderWithQueryClient = (ui: React.ReactElement) => }, }, }) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - vi.mock('@/app/components/app/store', () => ({ useStore: (selector: (state: Record) => unknown) => selector({ appDetail: mockAppDetail, diff --git a/web/__tests__/app/app-publisher-flow.test.tsx b/web/__tests__/app/app-publisher-flow.test.tsx index ce866653981..975b14f5898 100644 --- a/web/__tests__/app/app-publisher-flow.test.tsx +++ b/web/__tests__/app/app-publisher-flow.test.tsx @@ -35,16 +35,6 @@ const renderWithQueryClient = (ui: React.ReactElement) => }, }, }) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/app/components/app/store', () => ({ useStore: (selector: (state: Record) => unknown) => selector({ appDetail: mockAppDetail, @@ -173,12 +163,12 @@ describe('App Publisher Flow', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) - expect(screen.getByText('common.latestPublished')).toBeInTheDocument() - expect(screen.getByText('common.publishUpdate')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)common\.latestPublished(?=$|:)/)).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)common\.publishUpdate(?=$|:)/)).toBeInTheDocument() - fireEvent.click(screen.getByText('common.publishUpdate')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publishUpdate(?=$|:)/)) await waitFor(() => { expect(onPublish).toHaveBeenCalledTimes(1) @@ -193,13 +183,13 @@ describe('App Publisher Flow', () => { it('opens embedded modal and resolves the installed explore target', async () => { renderWithQueryClient() - fireEvent.click(screen.getByText('common.publish')) - fireEvent.click(screen.getByText('common.embedIntoSite')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) + fireEvent.click(screen.getByText(/(?:^|\.)common\.embedIntoSite(?=$|:)/)) expect(screen.getByTestId('embedded-modal')).toBeInTheDocument() - fireEvent.click(screen.getByText('common.publish')) - fireEvent.click(screen.getByText('common.openInExplore')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) + fireEvent.click(screen.getByText(/(?:^|\.)common\.openInExplore(?=$|:)/)) await waitFor(() => { expect(mockFetchInstalledAppList).toHaveBeenCalledWith('app-1') @@ -214,11 +204,11 @@ describe('App Publisher Flow', () => { renderWithQueryClient() - fireEvent.click(screen.getByText('common.publish')) - fireEvent.click(screen.getByText('common.openInExplore')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) + fireEvent.click(screen.getByText(/(?:^|\.)common\.openInExplore(?=$|:)/)) await waitFor(() => { - expect(mockToastError).toHaveBeenCalledWith('notPublishedYet') + expect(mockToastError).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)notPublishedYet(?=$|:)/)) }) }) }) diff --git a/web/__tests__/billing/cloud-plan-payment-flow.test.tsx b/web/__tests__/billing/cloud-plan-payment-flow.test.tsx index d064ba5cfbd..f8aacfc7084 100644 --- a/web/__tests__/billing/cloud-plan-payment-flow.test.tsx +++ b/web/__tests__/billing/cloud-plan-payment-flow.test.tsx @@ -51,10 +51,6 @@ vi.mock('jotai', async (importOriginal) => { return createAppContextStateJotaiMock(importOriginal) }) -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en-US', -})) - // ─── Service mocks ─────────────────────────────────────────────────────────── vi.mock('@/service/billing', () => ({ fetchSubscriptionUrls: (...args: unknown[]) => mockFetchSubscriptionUrls(...args), diff --git a/web/__tests__/billing/education-verification-flow.test.tsx b/web/__tests__/billing/education-verification-flow.test.tsx index 628ec2812c1..67a1ff1a534 100644 --- a/web/__tests__/billing/education-verification-flow.test.tsx +++ b/web/__tests__/billing/education-verification-flow.test.tsx @@ -73,10 +73,6 @@ vi.mock('@/context/modal-context', () => ({ }), })) -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en-US', -})) - // ─── Service mocks ─────────────────────────────────────────────────────────── vi.mock('@/service/use-education', () => ({ useEducationVerify: () => ({ diff --git a/web/__tests__/billing/self-hosted-plan-flow.test.tsx b/web/__tests__/billing/self-hosted-plan-flow.test.tsx index 9726ffa6bb2..92d7975990a 100644 --- a/web/__tests__/billing/self-hosted-plan-flow.test.tsx +++ b/web/__tests__/billing/self-hosted-plan-flow.test.tsx @@ -46,10 +46,6 @@ vi.mock('jotai', async (importOriginal) => { return createAppContextStateJotaiMock(importOriginal) }) -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en-US', -})) - vi.mock('@/hooks/use-theme', () => ({ default: () => ({ theme: 'light' }), useTheme: () => ({ theme: 'light' }), diff --git a/web/__tests__/custom/custom-page-flow.test.tsx b/web/__tests__/custom/custom-page-flow.test.tsx index 0dbbd599e01..c4af4166dfc 100644 --- a/web/__tests__/custom/custom-page-flow.test.tsx +++ b/web/__tests__/custom/custom-page-flow.test.tsx @@ -15,16 +15,6 @@ vi.mock('@/config', async (importOriginal) => { IS_CLOUD_EDITION: true, } }) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - vi.mock('@/context/provider-context', () => ({ useProviderContext: vi.fn(), })) diff --git a/web/__tests__/datasets/metadata-management-flow.test.tsx b/web/__tests__/datasets/metadata-management-flow.test.tsx index 40109b121f3..2f37b8c9cdd 100644 --- a/web/__tests__/datasets/metadata-management-flow.test.tsx +++ b/web/__tests__/datasets/metadata-management-flow.test.tsx @@ -16,15 +16,6 @@ import type { MetadataItemWithValueLength } from '@/app/components/datasets/meta import { renderHook } from '@testing-library/react' import { DataType } from '@/app/components/datasets/metadata/types' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - const { default: useCheckMetadataName } = await import( '@/app/components/datasets/metadata/hooks/use-check-metadata-name', ) diff --git a/web/__tests__/develop/develop-page-flow.test.tsx b/web/__tests__/develop/develop-page-flow.test.tsx index 175b5fe6959..09235c15327 100644 --- a/web/__tests__/develop/develop-page-flow.test.tsx +++ b/web/__tests__/develop/develop-page-flow.test.tsx @@ -34,11 +34,6 @@ vi.mock('@/app/components/app/store', () => ({ return selector({ appDetail: storeAppDetail }) }, })) - -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - vi.mock('@/hooks/use-theme', () => ({ default: () => ({ theme: Theme.light }), })) diff --git a/web/__tests__/explore/explore-app-list-flow.test.tsx b/web/__tests__/explore/explore-app-list-flow.test.tsx index 292852c9fe6..e0c16cce027 100644 --- a/web/__tests__/explore/explore-app-list-flow.test.tsx +++ b/web/__tests__/explore/explore-app-list-flow.test.tsx @@ -21,7 +21,7 @@ type MockAppContext = { const mockUseAppContext = vi.hoisted(() => vi.fn<() => MockAppContext>()) -const allCategoriesEn = 'explore.apps.allCategories:{"lng":"en"}' +const allCategoriesEn = 'explore.apps.allCategories:{"lng":"en-US"}' let mockTabValue = allCategoriesEn const mockSetTab = vi.fn() let mockExploreData: { categories: string[], allList: App[] } | undefined @@ -235,8 +235,8 @@ const mockMemberRole = (hasEditPermission: boolean) => { } as unknown as ReturnType) } -const localeInput = { query: { language: 'en' } } -const exploreAppListQueryKey = ['console', 'explore', 'apps', 'get', localeInput, 'en'] +const localeInput = { query: { language: 'en-US' } } +const exploreAppListQueryKey = ['console', 'explore', 'apps', 'get', localeInput, 'en-US'] const homeContinueWorkAppsInput = { query: { page: 1, diff --git a/web/__tests__/goto-anything/search-error-handling.test.ts b/web/__tests__/goto-anything/search-error-handling.test.ts index 2e362ca2804..3a495834cd3 100644 --- a/web/__tests__/goto-anything/search-error-handling.test.ts +++ b/web/__tests__/goto-anything/search-error-handling.test.ts @@ -14,17 +14,6 @@ import { fetchAppList } from '@/service/apps' import { postMarketplace } from '@/service/base' import { fetchDatasets } from '@/service/datasets' -// Mock react-i18next before importing modules that use it -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - getI18n: () => ({ - t: withSelectorKey((key: string) => key), - language: 'en', - }), - }) -}) - // Mock API functions vi.mock('@/service/base', () => ({ postMarketplace: vi.fn(), diff --git a/web/__tests__/header/account-dropdown-flow.test.tsx b/web/__tests__/header/account-dropdown-flow.test.tsx index 3184cbc185a..b25934eeebe 100644 --- a/web/__tests__/header/account-dropdown-flow.test.tsx +++ b/web/__tests__/header/account-dropdown-flow.test.tsx @@ -5,6 +5,10 @@ import { Plan } from '@/app/components/billing/type' import AccountDropdown from '@/app/components/header/account-dropdown' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' +vi.mock('@/context/i18n', () => ({ + useDocLink: () => (path: string) => `https://docs.example.com${path}`, +})) + const { mockAppContextState, mockPush, @@ -92,10 +96,6 @@ vi.mock('@/context/modal-context', () => ({ }), })) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.example.com${path}`, -})) - vi.mock('@/service/use-common', async importOriginal => ({ ...await importOriginal(), useLogout: () => ({ diff --git a/web/__tests__/plugins/plugin-card-rendering.test.tsx b/web/__tests__/plugins/plugin-card-rendering.test.tsx index 5ba81dd001b..c7894b94b85 100644 --- a/web/__tests__/plugins/plugin-card-rendering.test.tsx +++ b/web/__tests__/plugins/plugin-card-rendering.test.tsx @@ -10,19 +10,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' let mockTheme = 'light' -vi.mock('#i18n', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en_US', -})) - vi.mock('@/hooks/use-theme', () => ({ default: () => ({ theme: mockTheme }), })) diff --git a/web/__tests__/plugins/plugin-page-shell-flow.test.tsx b/web/__tests__/plugins/plugin-page-shell-flow.test.tsx index dfc059a38b1..75696378014 100644 --- a/web/__tests__/plugins/plugin-page-shell-flow.test.tsx +++ b/web/__tests__/plugins/plugin-page-shell-flow.test.tsx @@ -9,16 +9,6 @@ const mockFetchManifestFromMarketPlace = vi.fn() const { mockRouterReplace } = vi.hoisted(() => ({ mockRouterReplace: vi.fn(), })) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - vi.mock('@/utils', async (importOriginal) => { const actual = await importOriginal() return { @@ -38,10 +28,6 @@ vi.mock('@/next/navigation', () => ({ }), })) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.example.com${path}`, -})) - vi.mock('@/context/account-state', async (importOriginal) => { const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateAtomMock(importOriginal, () => ({ diff --git a/web/__tests__/rag-pipeline/test-run-flow.test.ts b/web/__tests__/rag-pipeline/test-run-flow.test.ts index b744c7a0cf2..d9af2172a21 100644 --- a/web/__tests__/rag-pipeline/test-run-flow.test.ts +++ b/web/__tests__/rag-pipeline/test-run-flow.test.ts @@ -8,15 +8,6 @@ import { act, renderHook } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' import { BlockEnum } from '@/app/components/workflow/types' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - // Mutable holder so mock data can reference BlockEnum after imports const mockNodesHolder = vi.hoisted(() => ({ value: [] as Record[] })) diff --git a/web/__tests__/share/text-generation-mode-flow.test.tsx b/web/__tests__/share/text-generation-mode-flow.test.tsx index f30108ec23e..a05599250aa 100644 --- a/web/__tests__/share/text-generation-mode-flow.test.tsx +++ b/web/__tests__/share/text-generation-mode-flow.test.tsx @@ -5,16 +5,6 @@ import TextGeneration from '@/app/components/share/text-generation' const useSearchParamsMock = vi.fn(() => new URLSearchParams()) const mockUseTextGenerationAppState = vi.fn() const mockUseTextGenerationBatch = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - vi.mock('@/next/navigation', () => ({ useSearchParams: () => useSearchParamsMock(), })) diff --git a/web/__tests__/tools/provider-list-shell-flow.test.tsx b/web/__tests__/tools/provider-list-shell-flow.test.tsx index 3a8135a675e..1ffd9bf2fcf 100644 --- a/web/__tests__/tools/provider-list-shell-flow.test.tsx +++ b/web/__tests__/tools/provider-list-shell-flow.test.tsx @@ -7,16 +7,6 @@ import { CollectionType } from '@/app/components/tools/types' import { createNuqsTestWrapper } from '@/test/nuqs-testing' const mockInvalidateInstalledPluginList = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - vi.mock('@/app/components/plugins/hooks', () => ({ useTags: () => ({ getTagLabel: (name: string) => name, diff --git a/web/app/components/app/annotation/__tests__/index.spec.tsx b/web/app/components/app/annotation/__tests__/index.spec.tsx index a36c65c55d9..e4120d6f114 100644 --- a/web/app/components/app/annotation/__tests__/index.spec.tsx +++ b/web/app/components/app/annotation/__tests__/index.spec.tsx @@ -21,6 +21,10 @@ import { AppModeEnum } from '@/types/app' import Annotation from '../index' import { AnnotationEnableStatus, JobStatus } from '../type' +vi.mock('@/context/i18n', () => ({ + useDocLink: () => (path: string) => `https://docs.example.com${path}`, +})) + vi.mock('ahooks', () => ({ useDebounce: (value: any) => value, })) @@ -41,10 +45,6 @@ vi.mock('@/context/provider-context', () => ({ useProviderContext: vi.fn(), })) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.example.com${path}`, -})) - vi.mock('../filter', () => ({ default: ({ children }: { children: React.ReactNode }) => (
{children}
diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-downloader.spec.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-downloader.spec.tsx index 69574564eb9..ec51a5c66e0 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-downloader.spec.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-downloader.spec.tsx @@ -7,6 +7,10 @@ import { useLocale } from '@/context/i18n' import { LanguagesSupported } from '@/i18n-config/language' import CSVDownload from '../csv-downloader' +vi.mock('@/context/i18n', () => ({ + useLocale: vi.fn(() => 'en-US'), +})) + const downloaderProps: any[] = [] vi.mock('react-papaparse', () => ({ @@ -19,10 +23,6 @@ vi.mock('react-papaparse', () => ({ })), })) -vi.mock('@/context/i18n', () => ({ - useLocale: vi.fn(() => 'en-US'), -})) - const renderWithLocale = (locale: Locale) => { ;(useLocale as Mock).mockReturnValue(locale) return render() diff --git a/web/app/components/app/app-publisher/__tests__/features-wrapper.spec.tsx b/web/app/components/app/app-publisher/__tests__/features-wrapper.spec.tsx index 0a307dfe2ba..824a0f5de2d 100644 --- a/web/app/components/app/app-publisher/__tests__/features-wrapper.spec.tsx +++ b/web/app/components/app/app-publisher/__tests__/features-wrapper.spec.tsx @@ -31,16 +31,6 @@ const mockFeatures = { number_limits: 3, }, } - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/app/components/app/app-publisher', () => ({ AppPublisher: (props: Record) => { mockAppPublisherProps.current = props @@ -120,7 +110,7 @@ describe('FeaturesWrappedAppPublisher', () => { ) fireEvent.click(screen.getByText('restore-through-wrapper')) - fireEvent.click(screen.getByRole('button', { name: 'operation.confirm' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.confirm(?=$|:)/ })) await waitFor(() => { expect(publishedConfig.modelConfig.resetAppConfig).toHaveBeenCalledTimes(1) @@ -151,7 +141,7 @@ describe('FeaturesWrappedAppPublisher', () => { fireEvent.click(screen.getByText('restore-through-wrapper')) const dialog = screen.getByRole('alertdialog') - fireEvent.click(within(dialog).getByRole('button', { name: 'operation.cancel' })) + fireEvent.click(within(dialog).getByRole('button', { name: /(?:^|\.)operation\.cancel(?=$|:)/ })) await waitFor(() => { expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() diff --git a/web/app/components/app/app-publisher/__tests__/index.spec.tsx b/web/app/components/app/app-publisher/__tests__/index.spec.tsx index a796f908f90..b36d83a079d 100644 --- a/web/app/components/app/app-publisher/__tests__/index.spec.tsx +++ b/web/app/components/app/app-publisher/__tests__/index.spec.tsx @@ -37,16 +37,6 @@ const hotkeyMocks = vi.hoisted(() => ({ let mockAppDetail: Record | null = null let mockWorkspacePermissionKeys: string[] = ['tool.manage'] -vi.mock('react-i18next', async () => { - const { withSelectorKey, withSelectorKeyProps } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - Trans: withSelectorKeyProps(({ i18nKey }: { i18nKey?: string }) => i18nKey ?? null), - }) -}) - vi.mock('@tanstack/react-hotkeys', () => ({ useHotkey: (hotkey: string, handler: (event: { preventDefault: () => void }) => void) => { hotkeyMocks.hotkeys.push(hotkey) @@ -272,7 +262,7 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) expect(screen.getByText('publisher-summary-publish'))!.toBeInTheDocument() expect(mockOnToggle).toHaveBeenCalledWith(true) @@ -297,7 +287,7 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-summary-publish')) await waitFor(() => { @@ -317,7 +307,7 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-embed')) expect(screen.getByTestId('embedded-modal'))!.toBeInTheDocument() @@ -338,15 +328,15 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-run-config')) - expect(screen.getByText('overview.appInfo.workflowLaunchHiddenInputs.title')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)overview\.appInfo\.workflowLaunchHiddenInputs\.title(?=$|:)/)).toBeInTheDocument() fireEvent.change(screen.getByLabelText('Secret'), { target: { value: 'top-secret' }, }) - fireEvent.click(screen.getByRole('button', { name: 'overview.appInfo.launch' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)overview\.appInfo\.launch(?=$|:)/ })) await waitFor(() => { expect(mockWindowOpen).toHaveBeenCalledWith( @@ -376,13 +366,13 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-batch-run-config')) fireEvent.change(screen.getByLabelText('Batch Secret'), { target: { value: 'batch-value' }, }) - fireEvent.click(screen.getByRole('button', { name: 'overview.appInfo.launch' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)overview\.appInfo\.launch(?=$|:)/ })) await waitFor(() => { expect(mockWindowOpen).toHaveBeenCalledWith( @@ -404,7 +394,7 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-workflow-tool')) expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() @@ -424,7 +414,7 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-workflow-tool')) expect(screen.queryByTestId('workflow-tool-drawer')).not.toBeInTheDocument() @@ -438,12 +428,12 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-embed')) fireEvent.click(screen.getByText('close-embedded-modal')) expect(screen.queryByTestId('embedded-modal')).not.toBeInTheDocument() - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-access-control')) expect(screen.getByTestId('access-control'))!.toBeInTheDocument() fireEvent.click(screen.getByText('close-access-control')) @@ -458,7 +448,7 @@ describe('AppPublisher', () => { ) const setQueryDataSpy = vi.spyOn(queryClient, 'setQueryData') - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-access-control')) expect(screen.getByTestId('access-control'))!.toBeInTheDocument() @@ -488,7 +478,7 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-open-in-explore')) await waitFor(() => { @@ -508,7 +498,7 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish').parentElement?.parentElement as HTMLElement) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/).parentElement?.parentElement as HTMLElement) expect(screen.queryByText('publisher-summary-publish')).not.toBeInTheDocument() expect(mockOnToggle).not.toHaveBeenCalled() @@ -535,7 +525,7 @@ describe('AppPublisher', () => { expect(mockOnPublish).toHaveBeenCalledTimes(1) }) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-summary-restore')) await waitFor(() => { @@ -565,7 +555,7 @@ describe('AppPublisher', () => { }) expect(mockTrackEvent).not.toHaveBeenCalled() - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-summary-restore')) await waitFor(() => { @@ -593,11 +583,11 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-open-in-explore')) await waitFor(() => { - expect(mockToastError).toHaveBeenCalledWith('notPublishedYet') + expect(mockToastError).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)notPublishedYet(?=$|:)/)) }) }) @@ -621,7 +611,7 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-open-in-explore')) await waitFor(() => { @@ -641,8 +631,8 @@ describe('AppPublisher', () => { { systemFeatures: { webapp_auth: { enabled: true }, enable_creators_platform: true } }, ) - fireEvent.click(screen.getByText('common.publish')) - fireEvent.click(screen.getByText('common.publishToMarketplace')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publishToMarketplace(?=$|:)/)) await waitFor(() => { expect(mockPublishToCreatorsPlatform).toHaveBeenCalledWith({ appID: 'app-1' }) @@ -663,11 +653,11 @@ describe('AppPublisher', () => { { systemFeatures: { webapp_auth: { enabled: true }, enable_creators_platform: true } }, ) - fireEvent.click(screen.getByText('common.publish')) - fireEvent.click(screen.getByText('common.publishToMarketplace')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publishToMarketplace(?=$|:)/)) await waitFor(() => { - expect(mockToastError).toHaveBeenCalledWith('common.publishToMarketplaceFailed') + expect(mockToastError).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)common\.publishToMarketplaceFailed(?=$|:)/)) }) }) @@ -679,11 +669,11 @@ describe('AppPublisher', () => { { systemFeatures: { webapp_auth: { enabled: true }, enable_creators_platform: true } }, ) - fireEvent.click(screen.getByText('common.publish')) - const marketplaceButton = screen.getByText('common.publishToMarketplace').closest('a, button, div[role="button"]') as HTMLElement + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) + const marketplaceButton = screen.getByText(/(?:^|\.)common\.publishToMarketplace(?=$|:)/).closest('a, button, div[role="button"]') as HTMLElement expect(marketplaceButton).toBeInTheDocument() // clicking should not call the API because publishedAt is undefined - fireEvent.click(screen.getByText('common.publishToMarketplace')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publishToMarketplace(?=$|:)/)) expect(mockPublishToCreatorsPlatform).not.toHaveBeenCalled() }) @@ -695,8 +685,8 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) - expect(screen.queryByText('common.publishToMarketplace')).not.toBeInTheDocument() + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) + expect(screen.queryByText(/(?:^|\.)common\.publishToMarketplace(?=$|:)/)).not.toBeInTheDocument() }) it('should keep access control open when app detail is unavailable during confirmation', async () => { @@ -708,7 +698,7 @@ describe('AppPublisher', () => { />, ) - fireEvent.click(screen.getByText('common.publish')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.publish(?=$|:)/)) fireEvent.click(screen.getByText('publisher-access-control')) fireEvent.click(screen.getByText('confirm-access-control')) diff --git a/web/app/components/app/app-publisher/__tests__/publish-with-multiple-model.spec.tsx b/web/app/components/app/app-publisher/__tests__/publish-with-multiple-model.spec.tsx index f11f9a00747..f39c10a9873 100644 --- a/web/app/components/app/app-publisher/__tests__/publish-with-multiple-model.spec.tsx +++ b/web/app/components/app/app-publisher/__tests__/publish-with-multiple-model.spec.tsx @@ -3,16 +3,6 @@ import * as React from 'react' import PublishWithMultipleModel from '../publish-with-multiple-model' const mockUseProviderContext = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/context/provider-context', () => ({ useProviderContext: () => mockUseProviderContext(), })) @@ -115,8 +105,8 @@ describe('PublishWithMultipleModel', () => { />, ) - expect(screen.getByRole('button', { name: 'operation.applyConfig' })).toBeDisabled() - expect(screen.queryByText('publishAs')).not.toBeInTheDocument() + expect(screen.getByRole('button', { name: /(?:^|\.)operation\.applyConfig(?=$|:)/ })).toBeDisabled() + expect(screen.queryByText(/(?:^|\.)publishAs(?=$|:)/)).not.toBeInTheDocument() }) it('should open matching model options and call onSelect', () => { @@ -135,9 +125,9 @@ describe('PublishWithMultipleModel', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'operation.applyConfig' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.applyConfig(?=$|:)/ })) - expect(screen.getByText('publishAs')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)publishAs(?=$|:)/)).toBeInTheDocument() fireEvent.click(screen.getByText('GPT-4o')) diff --git a/web/app/components/app/app-publisher/__tests__/sections.spec.tsx b/web/app/components/app/app-publisher/__tests__/sections.spec.tsx index 74f7d060b34..e64e7b51716 100644 --- a/web/app/components/app/app-publisher/__tests__/sections.spec.tsx +++ b/web/app/components/app/app-publisher/__tests__/sections.spec.tsx @@ -5,15 +5,6 @@ import { AccessMode } from '@/models/access-control' import { AppModeEnum } from '@/types/app' import { AccessModeDisplay, PublisherAccessSection, PublisherActionsSection, PublisherSummarySection } from '../sections' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('../publish-with-multiple-model', () => ({ default: ({ onSelect }: { onSelect: (item: Record) => void }) => ( @@ -81,7 +72,7 @@ describe('app-publisher sections', () => { />, ) - fireEvent.click(screen.getByText('common.restore')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.restore(?=$|:)/)) expect(handleRestore).toHaveBeenCalled() }) @@ -96,8 +87,8 @@ describe('app-publisher sections', () => { />, ) - expect(screen.getByText('publishApp.notSet')).toBeInTheDocument() - expect(screen.getByText('publishApp.notSetDesc')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)publishApp\.notSet(?=$|:)/)).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)publishApp\.notSetDesc(?=$|:)/)).toBeInTheDocument() }) it('should render the publish update action when the draft has not been published yet', () => { @@ -119,7 +110,7 @@ describe('app-publisher sections', () => { />, ) - expect(screen.getByText('common.publishUpdate')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)common\.publishUpdate(?=$|:)/)).toBeInTheDocument() }) it('should render multiple-model publishing', () => { @@ -167,7 +158,7 @@ describe('app-publisher sections', () => { />, ) - expect(screen.getByText('publishLimit.startNodeDesc')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)publishLimit\.startNodeDesc(?=$|:)/)).toBeInTheDocument() }) it('should render loading access state and access mode labels when enabled', () => { @@ -193,7 +184,7 @@ describe('app-publisher sections', () => { />, ) - expect(screen.getByText('accessControlDialog.accessItems.anyone')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)accessControlDialog\.accessItems\.anyone(?=$|:)/)).toBeInTheDocument() expect(render().container).toBeEmptyDOMElement() }) @@ -208,8 +199,8 @@ describe('app-publisher sections', () => { />, ) - expect(screen.queryByText('publishApp.title')).not.toBeInTheDocument() - expect(screen.queryByText('accessControlDialog.accessItems.anyone')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)publishApp\.title(?=$|:)/)).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)accessControlDialog\.accessItems\.anyone(?=$|:)/)).not.toBeInTheDocument() }) it('should render workflow actions, batch run links, and workflow tool configuration', () => { @@ -251,12 +242,12 @@ describe('app-publisher sections', () => { />, ) - expect(screen.getByText('common.batchRunApp')).toHaveAttribute('data-link', 'https://example.com/app?mode=batch') - fireEvent.click(screen.getAllByRole('button', { name: 'operation.config' })[0]!) + expect(screen.getByText(/(?:^|\.)common\.batchRunApp(?=$|:)/)).toHaveAttribute('data-link', 'https://example.com/app?mode=batch') + fireEvent.click(screen.getAllByRole('button', { name: /(?:^|\.)operation\.config(?=$|:)/ })[0]!) expect(handleOpenRunConfig).toHaveBeenCalledWith('https://example.com/app') - fireEvent.click(screen.getAllByRole('button', { name: 'operation.config' })[1]!) + fireEvent.click(screen.getAllByRole('button', { name: /(?:^|\.)operation\.config(?=$|:)/ })[1]!) expect(handleOpenRunConfig).toHaveBeenCalledWith('https://example.com/app?mode=batch') - fireEvent.click(screen.getByText('common.openInExplore')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.openInExplore(?=$|:)/)) expect(handleOpenInExplore).toHaveBeenCalled() expect(screen.getByText('workflow-tool-configure')).toBeInTheDocument() expect(screen.getByText('workflow-disabled')).toBeInTheDocument() @@ -288,9 +279,9 @@ describe('app-publisher sections', () => { />, ) - fireEvent.click(screen.getByText('common.embedIntoSite')) + fireEvent.click(screen.getByText(/(?:^|\.)common\.embedIntoSite(?=$|:)/)) expect(handleEmbed).toHaveBeenCalled() - expect(screen.getByText('common.accessAPIReference')).toBeDisabled() + expect(screen.getByText(/(?:^|\.)common\.accessAPIReference(?=$|:)/)).toBeDisabled() rerender( { />, ) - expect(screen.queryByText('common.runApp')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)common\.runApp(?=$|:)/)).not.toBeInTheDocument() }) }) diff --git a/web/app/components/app/app-publisher/__tests__/version-info-modal.spec.tsx b/web/app/components/app/app-publisher/__tests__/version-info-modal.spec.tsx index 464eca40500..885f19b9fc6 100644 --- a/web/app/components/app/app-publisher/__tests__/version-info-modal.spec.tsx +++ b/web/app/components/app/app-publisher/__tests__/version-info-modal.spec.tsx @@ -3,15 +3,6 @@ import { toast } from '@langgenius/dify-ui/toast' import { fireEvent, render, screen } from '@testing-library/react' import VersionInfoModal from '../version-info-modal' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@langgenius/dify-ui/toast', () => ({ toast: { error: vi.fn(), @@ -54,9 +45,9 @@ describe('VersionInfoModal', () => { const [titleInput] = screen.getAllByRole('textbox') fireEvent.change(titleInput!, { target: { value: 'a'.repeat(16) } }) - fireEvent.click(screen.getByRole('button', { name: 'common.publish' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)common\.publish(?=$|:)/ })) - expect(toast.error).toHaveBeenCalledWith('versionHistory.editField.titleLengthLimit') + expect(toast.error).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)versionHistory\.editField\.titleLengthLimit(?=$|:)/)) expect(handlePublish).not.toHaveBeenCalled() }) @@ -80,7 +71,7 @@ describe('VersionInfoModal', () => { const [titleInput, notesInput] = screen.getAllByRole('textbox') fireEvent.change(titleInput!, { target: { value: 'Release 2' } }) fireEvent.change(notesInput!, { target: { value: 'Updated notes' } }) - fireEvent.click(screen.getByRole('button', { name: 'common.publish' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)common\.publish(?=$|:)/ })) expect(handlePublish).toHaveBeenCalledWith({ title: 'Release 2', @@ -117,7 +108,7 @@ describe('VersionInfoModal', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'operation.close' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.close(?=$|:)/ })) expect(handleClose).toHaveBeenCalledTimes(1) }) @@ -142,16 +133,16 @@ describe('VersionInfoModal', () => { const [titleInput, notesInput] = screen.getAllByRole('textbox') fireEvent.change(titleInput!, { target: { value: 'a'.repeat(16) } }) - fireEvent.click(screen.getByRole('button', { name: 'common.publish' })) - expect(toast.error).toHaveBeenCalledWith('versionHistory.editField.titleLengthLimit') + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)common\.publish(?=$|:)/ })) + expect(toast.error).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)versionHistory\.editField\.titleLengthLimit(?=$|:)/)) fireEvent.change(titleInput!, { target: { value: 'Release 3' } }) fireEvent.change(notesInput!, { target: { value: 'b'.repeat(101) } }) - fireEvent.click(screen.getByRole('button', { name: 'common.publish' })) - expect(toast.error).toHaveBeenCalledWith('versionHistory.editField.releaseNotesLengthLimit') + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)common\.publish(?=$|:)/ })) + expect(toast.error).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)versionHistory\.editField\.releaseNotesLengthLimit(?=$|:)/)) fireEvent.change(notesInput!, { target: { value: 'Stable release notes' } }) - fireEvent.click(screen.getByRole('button', { name: 'common.publish' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)common\.publish(?=$|:)/ })) expect(handlePublish).toHaveBeenCalledWith({ title: 'Release 3', diff --git a/web/app/components/app/configuration/config-prompt/__tests__/advanced-prompt-input.spec.tsx b/web/app/components/app/configuration/config-prompt/__tests__/advanced-prompt-input.spec.tsx index 44171e9e06c..22348b7d0a8 100644 --- a/web/app/components/app/configuration/config-prompt/__tests__/advanced-prompt-input.spec.tsx +++ b/web/app/components/app/configuration/config-prompt/__tests__/advanced-prompt-input.spec.tsx @@ -16,16 +16,6 @@ const mockOnDelete = vi.fn() const mockOnHideContextMissingTip = vi.fn() const mockCopy = vi.fn() const mockToastError = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('copy-to-clipboard', () => ({ default: (...args: unknown[]) => mockCopy(...args), })) @@ -175,7 +165,7 @@ describe('AdvancedPromptInput', () => { ) fireEvent.click(screen.getByText('blur-advanced')) - fireEvent.click(screen.getByText('operation.add')) + fireEvent.click(screen.getByText(/(?:^|\.)operation\.add(?=$|:)/)) expect(mockSetModelConfig).toHaveBeenCalledWith(expect.objectContaining({ configs: expect.objectContaining({ @@ -213,7 +203,7 @@ describe('AdvancedPromptInput', () => { const modalConfig = mockSetShowExternalDataToolModal.mock.calls[0]![0] expect(modalConfig.onValidateBeforeSaveCallback({ variable: 'existing_var' })).toBe(false) - expect(mockToastError).toHaveBeenCalledWith('varKeyError.keyAlreadyExists') + expect(mockToastError).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)varKeyError\.keyAlreadyExists(?=$|:)/)) modalConfig.onSaveCallback({ label: 'Search', diff --git a/web/app/components/app/configuration/config-prompt/__tests__/simple-prompt-input.spec.tsx b/web/app/components/app/configuration/config-prompt/__tests__/simple-prompt-input.spec.tsx index a04cf004c6f..fca684fa8b6 100644 --- a/web/app/components/app/configuration/config-prompt/__tests__/simple-prompt-input.spec.tsx +++ b/web/app/components/app/configuration/config-prompt/__tests__/simple-prompt-input.spec.tsx @@ -14,16 +14,6 @@ const mockSetPrevPromptConfig = vi.fn() const mockSetIntroduction = vi.fn() const mockOnChange = vi.fn() const mockToastError = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/hooks/use-breakpoints', () => ({ __esModule: true, default: () => 'desktop', @@ -154,9 +144,9 @@ describe('SimplePromptInput', () => { fireEvent.click(screen.getByText('blur-prompt')) - expect(screen.getByText('autoAddVar'))!.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)autoAddVar(?=$|:)/))!.toBeInTheDocument() - fireEvent.click(screen.getByText('operation.add')) + fireEvent.click(screen.getByText(/(?:^|\.)operation\.add(?=$|:)/)) expect(mockOnChange).toHaveBeenCalledWith('Hello {{new_var}}', [ expect.objectContaining({ @@ -186,7 +176,7 @@ describe('SimplePromptInput', () => { const modalConfig = mockSetShowExternalDataToolModal.mock.calls[0]![0] expect(modalConfig.onValidateBeforeSaveCallback({ variable: 'existing_var' })).toBe(false) - expect(mockToastError).toHaveBeenCalledWith('varKeyError.keyAlreadyExists') + expect(mockToastError).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)varKeyError\.keyAlreadyExists(?=$|:)/)) expect(modalConfig.onValidateBeforeSaveCallback({ variable: 'fresh_var' })).toBe(true) modalConfig.onSaveCallback(undefined) @@ -315,9 +305,9 @@ describe('SimplePromptInput', () => { ) fireEvent.click(screen.getByText('blur-prompt')) - expect(screen.getByText('autoAddVar'))!.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)autoAddVar(?=$|:)/))!.toBeInTheDocument() - fireEvent.click(screen.getByText('operation.cancel')) + fireEvent.click(screen.getByText(/(?:^|\.)operation\.cancel(?=$|:)/)) expect(mockOnChange).toHaveBeenCalledWith('Hello {{existing_var}}', []) }) }) diff --git a/web/app/components/app/configuration/config-var/config-modal/__tests__/index-logic.spec.tsx b/web/app/components/app/configuration/config-var/config-modal/__tests__/index-logic.spec.tsx index e9890047fed..897aecfac78 100644 --- a/web/app/components/app/configuration/config-var/config-modal/__tests__/index-logic.spec.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/__tests__/index-logic.spec.tsx @@ -12,16 +12,6 @@ import ConfigModal from '../index' const toastErrorSpy = vi.spyOn(toast, 'error').mockReturnValue('toast-error') let latestFormProps: Record | null = null - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('../form-fields', () => ({ default: (props: Record) => { latestFormProps = props diff --git a/web/app/components/app/configuration/config/agent/agent-tools/__tests__/setting-built-in-tool.spec.tsx b/web/app/components/app/configuration/config/agent/agent-tools/__tests__/setting-built-in-tool.spec.tsx index cd72279fe38..cbd2b2e19d0 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/__tests__/setting-built-in-tool.spec.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/__tests__/setting-built-in-tool.spec.tsx @@ -55,10 +55,6 @@ vi.mock('@/app/components/plugins/readme-panel/entrance', () => ({ ReadmeEntrance: ({ className }: { className?: string }) =>
readme
, })) -vi.mock('@/context/i18n', () => ({ - useLocale: vi.fn(() => 'en-US'), -})) - const createParameter = (overrides?: Partial): ToolParameter => ({ name: 'settingParam', label: { diff --git a/web/app/components/app/configuration/config/automatic/__tests__/automatic-btn.spec.tsx b/web/app/components/app/configuration/config/automatic/__tests__/automatic-btn.spec.tsx index ced7af3e1fa..d2e05542af6 100644 --- a/web/app/components/app/configuration/config/automatic/__tests__/automatic-btn.spec.tsx +++ b/web/app/components/app/configuration/config/automatic/__tests__/automatic-btn.spec.tsx @@ -1,15 +1,6 @@ import { fireEvent, render, screen } from '@testing-library/react' import AutomaticBtn from '../automatic-btn' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - describe('AutomaticBtn', () => { const mockOnClick = vi.fn() @@ -21,7 +12,7 @@ describe('AutomaticBtn', () => { it('should render the button with correct text', () => { render() - expect(screen.getByText('operation.automatic')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)operation\.automatic(?=$|:)/)).toBeInTheDocument() }) it('should render the sparkling icon', () => { diff --git a/web/app/components/app/configuration/config/automatic/__tests__/get-automatic-res.spec.tsx b/web/app/components/app/configuration/config/automatic/__tests__/get-automatic-res.spec.tsx index f8365c6e42d..241e3d1b79c 100644 --- a/web/app/components/app/configuration/config/automatic/__tests__/get-automatic-res.spec.tsx +++ b/web/app/components/app/configuration/config/automatic/__tests__/get-automatic-res.spec.tsx @@ -14,16 +14,6 @@ let mockDefaultModel: { } | null = null let mockInstructionTemplate: { data: string } | undefined - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@langgenius/dify-ui/toast', () => ({ toast: { error: (...args: unknown[]) => mockToastError(...args), @@ -161,7 +151,7 @@ describe('GetAutomaticRes', () => { expect(screen.getByTestId('basic-editor')).toHaveTextContent('template instruction') }) - fireEvent.click(screen.getByText('generate.template.pythonDebugger.name')) + fireEvent.click(screen.getByText(/(?:^|\.)generate\.template\.pythonDebugger\.name(?=$|:)/)) await waitFor(() => { expect(screen.getByTestId('basic-editor')).toHaveTextContent('generate.template.pythonDebugger.instruction') @@ -186,9 +176,9 @@ describe('GetAutomaticRes', () => { />, ) - fireEvent.click(screen.getByText('generate.generate')) + fireEvent.click(screen.getByText(/(?:^|\.)generate\.generate(?=$|:)/)) - expect(mockToastError).toHaveBeenCalledWith('errorMsg.fieldRequired') + expect(mockToastError).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)errorMsg\.fieldRequired(?=$|:)/)) expect(mockGenerateBasicAppFirstTimeRule).not.toHaveBeenCalled() expect(screen.getByText('result-placeholder')).toBeInTheDocument() }) @@ -212,7 +202,7 @@ describe('GetAutomaticRes', () => { ) fireEvent.click(screen.getByText('set-basic-instruction')) - fireEvent.click(screen.getByText('generate.generate')) + fireEvent.click(screen.getByText(/(?:^|\.)generate\.generate(?=$|:)/)) await waitFor(() => { expect(mockGenerateBasicAppFirstTimeRule).toHaveBeenCalledWith(expect.objectContaining({ @@ -228,10 +218,10 @@ describe('GetAutomaticRes', () => { fireEvent.click(screen.getByText('apply-result')) await waitFor(() => { - expect(screen.getByText('generate.overwriteTitle')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)generate\.overwriteTitle(?=$|:)/)).toBeInTheDocument() }) - fireEvent.click(screen.getByRole('button', { name: 'operation.confirm' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.confirm(?=$|:)/ })) expect(mockOnFinished).toHaveBeenCalledWith(expect.objectContaining({ modified: 'generated prompt', @@ -259,7 +249,7 @@ describe('GetAutomaticRes', () => { ) fireEvent.click(screen.getByText('set-basic-instruction')) - fireEvent.click(screen.getByText('generate.generate')) + fireEvent.click(screen.getByText(/(?:^|\.)generate\.generate(?=$|:)/)) await waitFor(() => { expect(screen.getByTestId('result-panel')).toHaveTextContent('generated prompt') @@ -268,7 +258,7 @@ describe('GetAutomaticRes', () => { fireEvent.click(screen.getByText('apply-result')) const dialog = await screen.findByRole('alertdialog') - fireEvent.click(within(dialog).getByRole('button', { name: 'operation.cancel' })) + fireEvent.click(within(dialog).getByRole('button', { name: /(?:^|\.)operation\.cancel(?=$|:)/ })) await waitFor(() => { expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() @@ -298,7 +288,7 @@ describe('GetAutomaticRes', () => { fireEvent.click(screen.getByText('set-workflow-instruction')) fireEvent.click(screen.getByText('set-idea-output')) - fireEvent.click(screen.getByText('generate.generate')) + fireEvent.click(screen.getByText(/(?:^|\.)generate\.generate(?=$|:)/)) await waitFor(() => { expect(mockGenerateRule).toHaveBeenCalledWith(expect.objectContaining({ diff --git a/web/app/components/app/configuration/config/automatic/__tests__/idea-output.spec.tsx b/web/app/components/app/configuration/config/automatic/__tests__/idea-output.spec.tsx index 71b6c06b583..ab46f1507f6 100644 --- a/web/app/components/app/configuration/config/automatic/__tests__/idea-output.spec.tsx +++ b/web/app/components/app/configuration/config/automatic/__tests__/idea-output.spec.tsx @@ -1,31 +1,22 @@ import { fireEvent, render, screen } from '@testing-library/react' import IdeaOutput from '../idea-output' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - describe('IdeaOutput', () => { it('should toggle the fold state and propagate textarea changes when expanded', () => { const onChange = vi.fn() render() - expect(screen.queryByPlaceholderText('generate.idealOutputPlaceholder')).not.toBeInTheDocument() + expect(screen.queryByPlaceholderText(/(?:^|\.)generate\.idealOutputPlaceholder(?=$|:)/)).not.toBeInTheDocument() - fireEvent.click(screen.getByText('generate.idealOutput')) + fireEvent.click(screen.getByText(/(?:^|\.)generate\.idealOutput(?=$|:)/)) - const textarea = screen.getByPlaceholderText('generate.idealOutputPlaceholder') + const textarea = screen.getByPlaceholderText(/(?:^|\.)generate\.idealOutputPlaceholder(?=$|:)/) fireEvent.change(textarea, { target: { value: 'Updated idea' } }) expect(onChange).toHaveBeenCalledWith('Updated idea') - fireEvent.click(screen.getByText('generate.idealOutput')) + fireEvent.click(screen.getByText(/(?:^|\.)generate\.idealOutput(?=$|:)/)) - expect(screen.queryByPlaceholderText('generate.idealOutputPlaceholder')).not.toBeInTheDocument() + expect(screen.queryByPlaceholderText(/(?:^|\.)generate\.idealOutputPlaceholder(?=$|:)/)).not.toBeInTheDocument() }) }) diff --git a/web/app/components/app/configuration/config/automatic/__tests__/instruction-editor.spec.tsx b/web/app/components/app/configuration/config/automatic/__tests__/instruction-editor.spec.tsx index fc90426f29f..2f26ceb7e02 100644 --- a/web/app/components/app/configuration/config/automatic/__tests__/instruction-editor.spec.tsx +++ b/web/app/components/app/configuration/config/automatic/__tests__/instruction-editor.spec.tsx @@ -4,16 +4,6 @@ import InstructionEditor from '../instruction-editor' import { GeneratorType } from '../types' const mockEmit = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/context/event-emitter', () => ({ useEventEmitterContextContext: () => ({ eventEmitter: { @@ -63,7 +53,7 @@ describe('InstructionEditor', () => { />, ) - expect(screen.getByText('generate.instructionPlaceHolderTitle')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)generate\.instructionPlaceHolderTitle(?=$|:)/)).toBeInTheDocument() expect(screen.getByTestId('current-block')).toHaveTextContent('true') expect(screen.getByTestId('error-block')).toHaveTextContent('false') @@ -81,11 +71,11 @@ describe('InstructionEditor', () => { />, ) - expect(screen.getByText('generate.codeGenInstructionPlaceHolderLine')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)generate\.codeGenInstructionPlaceHolderLine(?=$|:)/)).toBeInTheDocument() expect(screen.getByTestId('error-block')).toHaveTextContent('true') expect(screen.getByTestId('last-run-block')).toHaveTextContent('true') - fireEvent.click(screen.getByRole('button', { name: 'generate.insertContext' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)generate\.insertContext(?=$|:)/ })) expect(mockEmit).toHaveBeenCalledWith(expect.objectContaining({ instanceId: 'editor-1', diff --git a/web/app/components/app/configuration/config/automatic/__tests__/prompt-res-in-workflow.spec.tsx b/web/app/components/app/configuration/config/automatic/__tests__/prompt-res-in-workflow.spec.tsx index d12652f3485..cb2050276c0 100644 --- a/web/app/components/app/configuration/config/automatic/__tests__/prompt-res-in-workflow.spec.tsx +++ b/web/app/components/app/configuration/config/automatic/__tests__/prompt-res-in-workflow.spec.tsx @@ -4,16 +4,6 @@ import PromptResInWorkflow from '../prompt-res-in-workflow' const mockUseAvailableVarList = vi.fn() const mockPromptRes = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/app/components/workflow/nodes/_base/hooks/use-available-var-list', () => ({ default: (...args: unknown[]) => mockUseAvailableVarList(...args), })) @@ -66,7 +56,7 @@ describe('PromptResInWorkflow', () => { type: BlockEnum.KnowledgeRetrieval, }), 'sys': expect.objectContaining({ - title: 'blocks.start', + title: 'workflow.blocks.start', type: BlockEnum.Start, }), }), diff --git a/web/app/components/app/configuration/config/automatic/__tests__/result.spec.tsx b/web/app/components/app/configuration/config/automatic/__tests__/result.spec.tsx index 9e4d7de555e..7c41a0b8c4a 100644 --- a/web/app/components/app/configuration/config/automatic/__tests__/result.spec.tsx +++ b/web/app/components/app/configuration/config/automatic/__tests__/result.spec.tsx @@ -8,16 +8,6 @@ const mockPromptRes = vi.fn() const mockPromptResInWorkflow = vi.fn() const mockSetCurrentVersionIndex = vi.fn() const mockOnApply = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('copy-to-clipboard', () => ({ default: (...args: unknown[]) => mockCopy(...args), })) @@ -89,11 +79,11 @@ describe('Result', () => { fireEvent.click(screen.getByTestId('version-selector')) fireEvent.click(screen.getAllByRole('button')[1]!) - fireEvent.click(screen.getByText('generate.apply')) + fireEvent.click(screen.getByText(/(?:^|\.)generate\.apply(?=$|:)/)) expect(mockSetCurrentVersionIndex).toHaveBeenCalledWith(1) expect(mockCopy).toHaveBeenCalledWith('generated output') - expect(toast.success).toHaveBeenCalledWith('actionMsg.copySuccessfully') + expect(toast.success).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)actionMsg\.copySuccessfully(?=$|:)/)) expect(mockOnApply).toHaveBeenCalled() expect(mockPromptRes).toHaveBeenCalledWith(expect.objectContaining({ workflowVariableBlock: { show: false }, diff --git a/web/app/components/app/configuration/config/automatic/__tests__/version-selector.spec.tsx b/web/app/components/app/configuration/config/automatic/__tests__/version-selector.spec.tsx index 86001907752..47dd131eba5 100644 --- a/web/app/components/app/configuration/config/automatic/__tests__/version-selector.spec.tsx +++ b/web/app/components/app/configuration/config/automatic/__tests__/version-selector.spec.tsx @@ -1,15 +1,6 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' import VersionSelector from '../version-selector' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - describe('VersionSelector', () => { it('should not open the selector when only one version exists', () => { const onChange = vi.fn() @@ -22,9 +13,9 @@ describe('VersionSelector', () => { />, ) - fireEvent.click(screen.getByText('generate.version 1 · generate.latest')) + fireEvent.click(screen.getByText(/(?:^|\.)generate\.version 1 · (?:.*\.)?generate\.latest$/)) - expect(screen.queryByText('generate.versions')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)generate\.versions(?=$|:)/)).not.toBeInTheDocument() expect(onChange).not.toHaveBeenCalled() }) @@ -39,16 +30,16 @@ describe('VersionSelector', () => { />, ) - fireEvent.click(screen.getByText('generate.version 3 · generate.latest')) + fireEvent.click(screen.getByText(/(?:^|\.)generate\.version 3 · (?:.*\.)?generate\.latest$/)) - expect(screen.getByText('generate.versions')).toBeInTheDocument() - expect(screen.getByText('generate.version 1')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)generate\.versions(?=$|:)/)).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)generate\.version 1$/)).toBeInTheDocument() - fireEvent.click(screen.getByText('generate.version 1')) + fireEvent.click(screen.getByText(/(?:^|\.)generate\.version 1$/)) expect(onChange).toHaveBeenCalledWith(0) await waitFor(() => { - expect(screen.queryByText('generate.versions')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)generate\.versions(?=$|:)/)).not.toBeInTheDocument() }) }) }) diff --git a/web/app/components/app/configuration/config/code-generator/__tests__/get-code-generator-res.spec.tsx b/web/app/components/app/configuration/config/code-generator/__tests__/get-code-generator-res.spec.tsx index 74052b9fa34..f5b43897d1d 100644 --- a/web/app/components/app/configuration/config/code-generator/__tests__/get-code-generator-res.spec.tsx +++ b/web/app/components/app/configuration/config/code-generator/__tests__/get-code-generator-res.spec.tsx @@ -14,16 +14,6 @@ let mockDefaultModel: { } | null = null let mockInstructionTemplate: { data: string } | undefined - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@langgenius/dify-ui/toast', () => ({ toast: { error: (...args: unknown[]) => mockToastError(...args), @@ -168,9 +158,9 @@ describe('GetCodeGeneratorResModal', () => { />, ) - fireEvent.click(screen.getByText('codegen.generate')) + fireEvent.click(screen.getByText(/(?:^|\.)codegen\.generate(?=$|:)/)) - expect(mockToastError).toHaveBeenCalledWith('errorMsg.fieldRequired') + expect(mockToastError).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)errorMsg\.fieldRequired(?=$|:)/)) expect(mockGenerateRule).not.toHaveBeenCalled() expect(screen.getByText('code-result-placeholder')).toBeInTheDocument() }) @@ -195,7 +185,7 @@ describe('GetCodeGeneratorResModal', () => { fireEvent.click(screen.getByText('set-code-instruction')) fireEvent.click(screen.getByText('set-code-output')) - fireEvent.click(screen.getByText('codegen.generate')) + fireEvent.click(screen.getByText(/(?:^|\.)codegen\.generate(?=$|:)/)) await waitFor(() => { expect(mockGenerateRule).toHaveBeenCalledWith(expect.objectContaining({ @@ -215,10 +205,10 @@ describe('GetCodeGeneratorResModal', () => { fireEvent.click(screen.getByText('apply-code-result')) await waitFor(() => { - expect(screen.getByText('codegen.overwriteConfirmTitle')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)codegen\.overwriteConfirmTitle(?=$|:)/)).toBeInTheDocument() }) - fireEvent.click(screen.getByRole('button', { name: 'operation.confirm' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.confirm(?=$|:)/ })) expect(mockOnFinished).toHaveBeenCalledWith(expect.objectContaining({ modified: 'print("hello")', @@ -246,7 +236,7 @@ describe('GetCodeGeneratorResModal', () => { fireEvent.click(screen.getByText('set-code-instruction')) fireEvent.click(screen.getByText('set-code-output')) - fireEvent.click(screen.getByText('codegen.generate')) + fireEvent.click(screen.getByText(/(?:^|\.)codegen\.generate(?=$|:)/)) await waitFor(() => { expect(screen.getByTestId('code-result-panel')).toHaveTextContent('print("hello")') @@ -255,7 +245,7 @@ describe('GetCodeGeneratorResModal', () => { fireEvent.click(screen.getByText('apply-code-result')) const dialog = await screen.findByRole('alertdialog') - fireEvent.click(within(dialog).getByRole('button', { name: 'operation.cancel' })) + fireEvent.click(within(dialog).getByRole('button', { name: /(?:^|\.)operation\.cancel(?=$|:)/ })) await waitFor(() => { expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() @@ -283,7 +273,7 @@ describe('GetCodeGeneratorResModal', () => { ) fireEvent.click(screen.getByText('set-code-instruction')) - fireEvent.click(screen.getByText('codegen.generate')) + fireEvent.click(screen.getByText(/(?:^|\.)codegen\.generate(?=$|:)/)) await waitFor(() => { expect(mockToastError).toHaveBeenCalledWith('generation failed') diff --git a/web/app/components/app/configuration/debug/__tests__/chat-user-input.spec.tsx b/web/app/components/app/configuration/debug/__tests__/chat-user-input.spec.tsx index c6fcd7fd289..9d48c0b7d22 100644 --- a/web/app/components/app/configuration/debug/__tests__/chat-user-input.spec.tsx +++ b/web/app/components/app/configuration/debug/__tests__/chat-user-input.spec.tsx @@ -5,16 +5,6 @@ import ChatUserInput from '../chat-user-input' const mockSetInputs = vi.fn() const mockUseContext = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('use-context-selector', () => ({ useContext: () => mockUseContext(), createContext: vi.fn(() => ({})), @@ -273,7 +263,7 @@ describe('ChatUserInput', () => { })) render() - expect(screen.getByText('panel.optional')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)panel\.optional(?=$|:)/)).toBeInTheDocument() }) it('should not show optional label for required fields', () => { @@ -284,7 +274,7 @@ describe('ChatUserInput', () => { })) render() - expect(screen.queryByText('panel.optional')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)panel\.optional(?=$|:)/)).not.toBeInTheDocument() }) it('should use key as label when name is not provided', () => { diff --git a/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx b/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx index 9982660426f..d9cf48c8880 100644 --- a/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx +++ b/web/app/components/app/configuration/hooks/__tests__/use-configuration.spec.tsx @@ -29,16 +29,6 @@ let mockTempStopState: string[] = [] let mockCurrentModelFeatures = ['vision'] let mockCurrentModelMode = ModelModeType.chat let mockAppPermissionKeys: string[] = [AppACLPermission.Edit, AppACLPermission.ReleaseAndVersion] - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('ahooks', async () => { const actual = await vi.importActual('ahooks') diff --git a/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal.spec.tsx b/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal.spec.tsx index 8a3cd142c5b..1e0993c6a97 100644 --- a/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal.spec.tsx +++ b/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal.spec.tsx @@ -4,16 +4,6 @@ import ExternalDataToolModal from '../external-data-tool-modal' const mockToastError = vi.fn() let mockLocale = 'en-US' - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@langgenius/dify-ui/toast', () => ({ toast: { error: (...args: unknown[]) => mockToastError(...args), @@ -102,15 +92,15 @@ describe('ExternalDataToolModal', () => { />, ) - fireEvent.change(screen.getByPlaceholderText('feature.tools.modal.name.placeholder'), { + fireEvent.change(screen.getByPlaceholderText(/(?:^|\.)feature\.tools\.modal\.name\.placeholder(?=$|:)/), { target: { value: 'Search' }, }) - fireEvent.change(screen.getByPlaceholderText('feature.tools.modal.variableName.placeholder'), { + fireEvent.change(screen.getByPlaceholderText(/(?:^|\.)feature\.tools\.modal\.variableName\.placeholder(?=$|:)/), { target: { value: 'search_api' }, }) - fireEvent.click(screen.getByText('operation.save')) + fireEvent.click(screen.getByText(/(?:^|\.)operation\.save(?=$|:)/)) - expect(mockToastError).toHaveBeenCalledWith('errorMessage.valueOfVarRequired') + expect(mockToastError).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)errorMessage\.valueOfVarRequired(?=$|:)/)) expect(mockOnSave).not.toHaveBeenCalled() }) @@ -126,10 +116,10 @@ describe('ExternalDataToolModal', () => { />, ) - fireEvent.change(screen.getByPlaceholderText('feature.tools.modal.name.placeholder'), { + fireEvent.change(screen.getByPlaceholderText(/(?:^|\.)feature\.tools\.modal\.name\.placeholder(?=$|:)/), { target: { value: 'Search' }, }) - fireEvent.change(screen.getByPlaceholderText('feature.tools.modal.variableName.placeholder'), { + fireEvent.change(screen.getByPlaceholderText(/(?:^|\.)feature\.tools\.modal\.variableName\.placeholder(?=$|:)/), { target: { value: 'search_api' }, }) fireEvent.click(screen.getByText('pick-extension')) @@ -142,7 +132,7 @@ describe('ExternalDataToolModal', () => { fireEvent.click(emojiButton!) fireEvent.click(screen.getByRole('button', { name: '#E4FBCC' })) fireEvent.click(screen.getByRole('button', { name: /iconPicker\.ok/ })) - fireEvent.click(screen.getByText('operation.save')) + fireEvent.click(screen.getByText(/(?:^|\.)operation\.save(?=$|:)/)) await waitFor(() => { expect(mockOnValidateBeforeSave).toHaveBeenCalledWith(expect.objectContaining({ @@ -170,7 +160,7 @@ describe('ExternalDataToolModal', () => { variable: 'search_api', })) - expect(screen.getByRole('link', { name: 'apiBasedExtension.link' })).toHaveAttribute( + expect(screen.getByRole('link', { name: /(?:^|\.)apiBasedExtension\.link(?=$|:)/ })).toHaveAttribute( 'href', 'https://docs.example.com/use-dify/workspace/api-extension/api-extension', ) @@ -191,14 +181,14 @@ describe('ExternalDataToolModal', () => { />, ) - fireEvent.change(screen.getByPlaceholderText('feature.tools.modal.name.placeholder'), { + fireEvent.change(screen.getByPlaceholderText(/(?:^|\.)feature\.tools\.modal\.name\.placeholder(?=$|:)/), { target: { value: 'Code Search' }, }) - fireEvent.change(screen.getByPlaceholderText('feature.tools.modal.variableName.placeholder'), { + fireEvent.change(screen.getByPlaceholderText(/(?:^|\.)feature\.tools\.modal\.variableName\.placeholder(?=$|:)/), { target: { value: 'code_search' }, }) fireEvent.click(screen.getByText('fill-form')) - fireEvent.click(screen.getByText('operation.save')) + fireEvent.click(screen.getByText(/(?:^|\.)operation\.save(?=$|:)/)) await waitFor(() => { expect(mockOnSave).toHaveBeenCalledWith(expect.objectContaining({ @@ -212,7 +202,7 @@ describe('ExternalDataToolModal', () => { })) }) - fireEvent.click(screen.getByText('operation.cancel')) + fireEvent.click(screen.getByText(/(?:^|\.)operation\.cancel(?=$|:)/)) expect(mockOnCancel).toHaveBeenCalled() }) }) diff --git a/web/app/components/app/create-from-dsl-modal/__tests__/dsl-confirm-modal.spec.tsx b/web/app/components/app/create-from-dsl-modal/__tests__/dsl-confirm-modal.spec.tsx index 51a51ef6ada..424ddceeb31 100644 --- a/web/app/components/app/create-from-dsl-modal/__tests__/dsl-confirm-modal.spec.tsx +++ b/web/app/components/app/create-from-dsl-modal/__tests__/dsl-confirm-modal.spec.tsx @@ -1,15 +1,6 @@ import { fireEvent, render, screen } from '@testing-library/react' import DSLConfirmModal from '../dsl-confirm-modal' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - describe('DSLConfirmModal', () => { it('should render the version details', () => { render( @@ -23,7 +14,7 @@ describe('DSLConfirmModal', () => { />, ) - expect(screen.getByText('newApp.appCreateDSLErrorTitle')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)newApp\.appCreateDSLErrorTitle(?=$|:)/)).toBeInTheDocument() expect(screen.getByText('1.0.0')).toBeInTheDocument() expect(screen.getByText('2.0.0')).toBeInTheDocument() }) @@ -39,8 +30,8 @@ describe('DSLConfirmModal', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'newApp.Cancel' })) - fireEvent.click(screen.getByRole('button', { name: 'newApp.Confirm' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)newApp\.Cancel(?=$|:)/ })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)newApp\.Confirm(?=$|:)/ })) expect(handleCancel).toHaveBeenCalledTimes(1) expect(handleConfirm).toHaveBeenCalledTimes(1) diff --git a/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx b/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx index 74c21f751b9..84d249d9481 100644 --- a/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx @@ -31,16 +31,6 @@ let mockPlanUsage = 0 let mockPlanTotal = 10 let mockWorkspacePermissionKeys: string[] = ['app.create_and_management'] const mockUserProfile = { id: 'user-1' } - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('ahooks', () => ({ useDebounceFn: (fn: (...args: any[]) => any) => ({ run: fn, @@ -202,10 +192,10 @@ describe('CreateFromDSLModal', () => { />, ) - expect(screen.getByText('importApp'))!.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)importApp(?=$|:)/))!.toBeInTheDocument() await waitFor(() => { - expect(screen.getByText('demo.yml'))!.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)demo\.yml(?=$|:)/))!.toBeInTheDocument() }) }) @@ -217,14 +207,14 @@ describe('CreateFromDSLModal', () => { expect(mockImportDSL).not.toHaveBeenCalled() await act(async () => { - fireEvent.click(screen.getByText('importFromDSLUrl')) + fireEvent.click(screen.getByText(/(?:^|\.)importFromDSLUrl(?=$|:)/)) }) expect( - screen.getByPlaceholderText('importFromDSLUrlPlaceholder'), + screen.getByPlaceholderText(/(?:^|\.)importFromDSLUrlPlaceholder(?=$|:)/), )!.toBeInTheDocument() const closeTrigger = screen - .getByText('importApp') + .getByText(/(?:^|\.)importApp(?=$|:)/) .parentElement ?.querySelector( '.cursor-pointer.items-center', @@ -268,7 +258,7 @@ describe('CreateFromDSLModal', () => { ) fireEvent.change( - screen.getByPlaceholderText('importFromDSLUrlPlaceholder'), + screen.getByPlaceholderText(/(?:^|\.)importFromDSLUrlPlaceholder(?=$|:)/), { target: { value: 'https://example.com/app.yml' }, }, @@ -354,7 +344,7 @@ describe('CreateFromDSLModal', () => { ) await waitFor(() => { - expect(screen.getByText('demo.yml'))!.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)demo\.yml(?=$|:)/))!.toBeInTheDocument() }) await act(async () => { @@ -377,11 +367,11 @@ describe('CreateFromDSLModal', () => { ) await waitFor(() => { - expect(screen.getByText('demo.yml'))!.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)demo\.yml(?=$|:)/))!.toBeInTheDocument() }) const removeButton = screen - .getByText('demo.yml') + .getByText(/(?:^|\.)demo\.yml(?=$|:)/) .closest('.group') ?.querySelector('button') as HTMLButtonElement await act(async () => { @@ -389,7 +379,7 @@ describe('CreateFromDSLModal', () => { }) await waitFor(() => { - expect(screen.queryByText('demo.yml')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)demo\.yml(?=$|:)/)).not.toBeInTheDocument() expect(getCreateButton())!.toBeDisabled() }) @@ -431,12 +421,12 @@ describe('CreateFromDSLModal', () => { }) expect( - screen.getAllByText('newApp.appCreateDSLErrorTitle')[0], + screen.getAllByText(/(?:^|\.)newApp\.appCreateDSLErrorTitle(?=$|:)/)[0], )!.toBeInTheDocument() await act(async () => { fireEvent.click( - screen.getAllByRole('button', { name: 'newApp.Confirm' })[0]!, + screen.getAllByRole('button', { name: /(?:^|\.)newApp\.Confirm(?=$|:)/ })[0]!, ) }) @@ -487,7 +477,7 @@ describe('CreateFromDSLModal', () => { }) expect( - screen.getByText('newApp.appCreateDSLErrorTitle'), + screen.getByText(/(?:^|\.)newApp\.appCreateDSLErrorTitle(?=$|:)/), )!.toBeInTheDocument() vi.useRealTimers() @@ -495,7 +485,7 @@ describe('CreateFromDSLModal', () => { await waitFor(() => { expect( - screen.queryByText('newApp.appCreateDSLErrorTitle'), + screen.queryByText(/(?:^|\.)newApp\.appCreateDSLErrorTitle(?=$|:)/), ).not.toBeInTheDocument() }) }) @@ -527,17 +517,17 @@ describe('CreateFromDSLModal', () => { }) expect( - screen.getByText('newApp.appCreateDSLErrorTitle'), + screen.getByText(/(?:^|\.)newApp\.appCreateDSLErrorTitle(?=$|:)/), )!.toBeInTheDocument() vi.useRealTimers() fireEvent.click( - screen.getAllByRole('button', { name: 'newApp.Cancel' }).at(-1)!, + screen.getAllByRole('button', { name: /(?:^|\.)newApp\.Cancel(?=$|:)/ }).at(-1)!, ) await waitFor(() => { expect( - screen.queryByText('newApp.appCreateDSLErrorTitle'), + screen.queryByText(/(?:^|\.)newApp\.appCreateDSLErrorTitle(?=$|:)/), ).not.toBeInTheDocument() }) }) @@ -670,7 +660,7 @@ describe('CreateFromDSLModal', () => { fireEvent.click(getCreateButton()) }) expect(toastMocks.error).toHaveBeenCalledTimes(2) - expect(toastMocks.error).toHaveBeenLastCalledWith('newApp.appCreateFailed') + expect(toastMocks.error).toHaveBeenLastCalledWith(expect.stringMatching(/(?:^|\.)newApp\.appCreateFailed(?=$|:)/)) }) it('should handle pending import confirmation failures and cancellation', async () => { @@ -703,10 +693,10 @@ describe('CreateFromDSLModal', () => { }) fireEvent.click( - screen.getAllByRole('button', { name: 'newApp.Cancel' }).at(-1)!, + screen.getAllByRole('button', { name: /(?:^|\.)newApp\.Cancel(?=$|:)/ }).at(-1)!, ) expect( - screen.queryByText('newApp.appCreateDSLErrorTitle'), + screen.queryByText(/(?:^|\.)newApp\.appCreateDSLErrorTitle(?=$|:)/), ).not.toBeInTheDocument() await act(async () => { @@ -715,17 +705,17 @@ describe('CreateFromDSLModal', () => { }) await act(async () => { fireEvent.click( - screen.getAllByRole('button', { name: 'newApp.Confirm' })[0]!, + screen.getAllByRole('button', { name: /(?:^|\.)newApp\.Confirm(?=$|:)/ })[0]!, ) }) expect(toastMocks.error).toHaveBeenCalledWith('Confirm failed') await act(async () => { fireEvent.click( - screen.getAllByRole('button', { name: 'newApp.Confirm' })[0]!, + screen.getAllByRole('button', { name: /(?:^|\.)newApp\.Confirm(?=$|:)/ })[0]!, ) }) expect(toastMocks.error).toHaveBeenCalledTimes(2) - expect(toastMocks.error).toHaveBeenLastCalledWith('newApp.appCreateFailed') + expect(toastMocks.error).toHaveBeenLastCalledWith(expect.stringMatching(/(?:^|\.)newApp\.appCreateFailed(?=$|:)/)) }) }) diff --git a/web/app/components/app/create-from-dsl-modal/__tests__/uploader.spec.tsx b/web/app/components/app/create-from-dsl-modal/__tests__/uploader.spec.tsx index 3f9c1bbfc37..b69addfc4c5 100644 --- a/web/app/components/app/create-from-dsl-modal/__tests__/uploader.spec.tsx +++ b/web/app/components/app/create-from-dsl-modal/__tests__/uploader.spec.tsx @@ -2,15 +2,6 @@ import { toast } from '@langgenius/dify-ui/toast' import { fireEvent, render, screen } from '@testing-library/react' import Uploader from '../uploader' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@langgenius/dify-ui/toast', () => ({ toast: { error: vi.fn(), @@ -66,7 +57,7 @@ describe('Uploader', () => { }, }) - expect(toast.error).toHaveBeenCalledWith('stepOne.uploader.validation.count') + expect(toast.error).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)stepOne\.uploader\.validation\.count(?=$|:)/)) expect(updateFile).not.toHaveBeenCalled() }) @@ -82,7 +73,7 @@ describe('Uploader', () => { />, ) - expect(screen.getByText('demo.yml')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)demo\.yml(?=$|:)/)).toBeInTheDocument() expect(screen.getByText('DSL')).toBeInTheDocument() fireEvent.click(screen.getByRole('button')) @@ -160,7 +151,7 @@ describe('Uploader', () => { const hiddenInput = getHiddenInput() const clickSpy = vi.spyOn(hiddenInput, 'click') - fireEvent.click(screen.getByRole('button', { name: 'dslUploader.browse' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)dslUploader\.browse(?=$|:)/ })) expect(clickSpy).toHaveBeenCalled() diff --git a/web/app/components/app/duplicate-modal/__tests__/index.spec.tsx b/web/app/components/app/duplicate-modal/__tests__/index.spec.tsx index 0c272e30604..a71a92abcf8 100644 --- a/web/app/components/app/duplicate-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/duplicate-modal/__tests__/index.spec.tsx @@ -3,16 +3,6 @@ import userEvent from '@testing-library/user-event' import DuplicateAppModal from '../index' const toastErrorMock = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/context/provider-context', () => ({ useProviderContext: () => ({ plan: { @@ -94,9 +84,9 @@ describe('DuplicateAppModal', () => { expect(input).toHaveValue('Updated App') await user.clear(input) - await user.click(screen.getByRole('button', { name: 'duplicate' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)duplicate(?=$|:)/ })) - expect(toastErrorMock).toHaveBeenCalledWith('appCustomize.nameRequired') + expect(toastErrorMock).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)appCustomize\.nameRequired(?=$|:)/)) expect(onConfirm).not.toHaveBeenCalled() expect(onHide).not.toHaveBeenCalled() }) @@ -127,7 +117,7 @@ describe('DuplicateAppModal', () => { await waitFor(() => { expect(screen.queryByPlaceholderText('Search emojis...')).not.toBeInTheDocument() }) - await user.click(screen.getByRole('button', { name: 'duplicate' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)duplicate(?=$|:)/ })) expect(onConfirm).toHaveBeenCalledWith({ name: 'Demo App', @@ -154,7 +144,7 @@ describe('DuplicateAppModal', () => { />, ) - await user.click(screen.getByRole('button', { name: 'operation.close' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)operation\.close(?=$|:)/ })) expect(onHide).toHaveBeenCalledTimes(1) }) @@ -195,7 +185,7 @@ describe('DuplicateAppModal', () => { await waitFor(() => { expect(screen.queryByPlaceholderText('Search emojis...')).not.toBeInTheDocument() }) - await user.click(screen.getByRole('button', { name: 'duplicate' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)duplicate(?=$|:)/ })) expect(onConfirm).toHaveBeenCalledWith(expect.objectContaining({ name: 'Image App', diff --git a/web/app/components/app/log/__tests__/filter.spec.tsx b/web/app/components/app/log/__tests__/filter.spec.tsx index 7bf614b032f..aba887fcc4f 100644 --- a/web/app/components/app/log/__tests__/filter.spec.tsx +++ b/web/app/components/app/log/__tests__/filter.spec.tsx @@ -5,19 +5,6 @@ import Filter, { TIME_PERIOD_MAPPING } from '../filter' let mockAnnotationsCountLoading = false let mockAnnotationsCountData: { count: number } | null = { count: 10 } -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { count?: number }) => { - if (options?.count !== undefined) - return `${key} (${options.count})` - return key - }), - }), - }) -}) - vi.mock('@/service/use-log', () => ({ useAnnotationsCount: () => ({ data: mockAnnotationsCountData, @@ -80,7 +67,7 @@ describe('Filter', () => { it('should render filter components', () => { render() - expect(screen.getByPlaceholderText('operation.search'))!.toBeInTheDocument() + expect(screen.getByPlaceholderText(/(?:^|\.)operation\.search(?=$|:)/))!.toBeInTheDocument() }) it('should return null when loading', () => { @@ -92,13 +79,13 @@ describe('Filter', () => { it('should render sort component in chat mode', () => { render() - expect(screen.getByPlaceholderText('operation.search'))!.toBeInTheDocument() + expect(screen.getByPlaceholderText(/(?:^|\.)operation\.search(?=$|:)/))!.toBeInTheDocument() }) it('should not render sort component when not in chat mode', () => { render() - expect(screen.getByPlaceholderText('operation.search'))!.toBeInTheDocument() + expect(screen.getByPlaceholderText(/(?:^|\.)operation\.search(?=$|:)/))!.toBeInTheDocument() }) }) @@ -132,7 +119,7 @@ describe('Filter', () => { it('should update keyword when typing in search input', () => { render() - const searchInput = screen.getByPlaceholderText('operation.search') + const searchInput = screen.getByPlaceholderText(/(?:^|\.)operation\.search(?=$|:)/) fireEvent.change(searchInput, { target: { value: 'test search' } }) expect(mockSetQueryParams).toHaveBeenCalledWith({ @@ -149,7 +136,7 @@ describe('Filter', () => { render() - const clearButton = screen.getByRole('button', { name: 'operation.clear' }) + const clearButton = screen.getByRole('button', { name: /(?:^|\.)operation\.clear(?=$|:)/ }) fireEvent.click(clearButton) expect(mockSetQueryParams).toHaveBeenCalledWith({ @@ -204,7 +191,7 @@ describe('Filter', () => { // Period '1' maps to 'today' in TIME_PERIOD_MAPPING // Period '1' maps to 'today' in TIME_PERIOD_MAPPING - expect(screen.getByText('filter.period.today'))!.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)filter\.period\.today(?=$|:)/))!.toBeInTheDocument() }) it('should display "last7days" when period is set to 2', () => { @@ -215,7 +202,7 @@ describe('Filter', () => { render() - expect(screen.getByText('filter.period.last7days'))!.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)filter\.period\.last7days(?=$|:)/))!.toBeInTheDocument() }) it('should display "allTime" when period is set to 9', () => { @@ -223,7 +210,7 @@ describe('Filter', () => { // Default period is '9' which maps to 'allTime' // Default period is '9' which maps to 'allTime' - expect(screen.getByText('filter.period.allTime'))!.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)filter\.period\.allTime(?=$|:)/))!.toBeInTheDocument() }) it('should display annotated status with count when annotation_status is annotated', () => { @@ -236,7 +223,7 @@ describe('Filter', () => { // The mock returns count: 10, so the text should include the count // The mock returns count: 10, so the text should include the count - expect(screen.getByText('filter.annotation.annotated (10)'))!.toBeInTheDocument() + expect(screen.getByText(/filter\.annotation\.annotated.*10/))!.toBeInTheDocument() }) it('should display not_annotated status when annotation_status is not_annotated', () => { @@ -247,7 +234,7 @@ describe('Filter', () => { render() - expect(screen.getByText('filter.annotation.not_annotated'))!.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)filter\.annotation\.not_annotated(?=$|:)/))!.toBeInTheDocument() }) it('should display all annotation status when annotation_status is all', () => { @@ -255,7 +242,7 @@ describe('Filter', () => { // Default annotation_status is 'all' // Default annotation_status is 'all' - expect(screen.getByText('filter.annotation.all'))!.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)filter\.annotation\.all(?=$|:)/))!.toBeInTheDocument() }) it('should return null when annotation count data is unavailable', () => { @@ -276,7 +263,7 @@ describe('Filter', () => { render() - expect(screen.getByPlaceholderText('operation.search'))!.toBeInTheDocument() + expect(screen.getByPlaceholderText(/(?:^|\.)operation\.search(?=$|:)/))!.toBeInTheDocument() }) it('should handle descending sort order', () => { @@ -288,7 +275,7 @@ describe('Filter', () => { render() - expect(screen.getByPlaceholderText('operation.search'))!.toBeInTheDocument() + expect(screen.getByPlaceholderText(/(?:^|\.)operation\.search(?=$|:)/))!.toBeInTheDocument() }) }) }) diff --git a/web/app/components/app/log/__tests__/index.spec.tsx b/web/app/components/app/log/__tests__/index.spec.tsx index ab3568d2511..4c89fcd0d91 100644 --- a/web/app/components/app/log/__tests__/index.spec.tsx +++ b/web/app/components/app/log/__tests__/index.spec.tsx @@ -4,21 +4,15 @@ import { APP_PAGE_LIMIT } from '@/config' import { AppModeEnum } from '@/types/app' import Logs from '../index' +vi.mock('@/context/i18n', () => ({ + useDocLink: () => (path: string) => `https://docs.example.com${path}`, +})) + const mockReplace = vi.fn() const mockUseChatConversations = vi.fn() const mockUseCompletionConversations = vi.fn() let mockSearchParams = new URLSearchParams() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('ahooks', async () => { return { useDebounce: (value: T) => value, @@ -36,10 +30,6 @@ vi.mock('@/next/navigation', () => ({ }), })) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.example.com${path}`, -})) - vi.mock('@/service/use-log', () => ({ useChatConversations: (...args: unknown[]) => mockUseChatConversations(...args), useCompletionConversations: (...args: unknown[]) => mockUseCompletionConversations(...args), @@ -109,9 +99,9 @@ describe('Logs', () => { expect(mockUseChatConversations).toHaveBeenCalledWith(expect.objectContaining({ appId: 'app-1', })) - expect(screen.getByRole('heading', { name: 'title' })).toBeInTheDocument() - expect(screen.getByText('description')).toBeInTheDocument() - expect(screen.getByRole('link', { name: 'operation.learnMore' })).toHaveAttribute('href', 'https://docs.example.com/use-dify/monitor/logs') + expect(screen.getByRole('heading', { name: /(?:^|\.)title(?=$|:)/ })).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)description(?=$|:)/)).toBeInTheDocument() + expect(screen.getByRole('link', { name: /(?:^|\.)operation\.learnMore(?=$|:)/ })).toHaveAttribute('href', 'https://docs.example.com/use-dify/monitor/logs') expect(screen.getByText('loading-logs')).toBeInTheDocument() }) diff --git a/web/app/components/app/log/__tests__/list.spec.tsx b/web/app/components/app/log/__tests__/list.spec.tsx index b5a4a1c5322..beecdc7dbfa 100644 --- a/web/app/components/app/log/__tests__/list.spec.tsx +++ b/web/app/components/app/log/__tests__/list.spec.tsx @@ -24,16 +24,6 @@ let mockShowPromptLogModal = false let mockShowAgentLogModal = false let mockCurrentLogItem: Record | undefined let mockCurrentLogModalActiveTab = 'messages' - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/hooks/use-timestamp', () => ({ default: () => ({ formatTime: (timestamp: number) => `formatted-${timestamp}`, @@ -311,7 +301,7 @@ describe('ConversationList', () => { searchParams: '?page=2&conversation_id=conversation-1', }) - fireEvent.click(await screen.findByRole('button', { name: 'operation.close' })) + fireEvent.click(await screen.findByRole('button', { name: /(?:^|\.)operation\.close(?=$|:)/ })) expect(mockOnRefresh).toHaveBeenCalledTimes(1) expect(mockSetShowPromptLogModal).toHaveBeenCalledWith(false) @@ -636,7 +626,7 @@ describe('ConversationList', () => { await waitFor(() => { expect(screen.getByTestId('chat-panel')).toBeInTheDocument() expect(screen.getByTestId('chat-panel')).toHaveTextContent('8') - expect(screen.getByText('detail.loading...')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)detail\.loading\.\.\.(?=$|:)/)).toBeInTheDocument() }) fireEvent.click(screen.getByText('chat-add-annotation')) diff --git a/web/app/components/app/log/__tests__/model-info.spec.tsx b/web/app/components/app/log/__tests__/model-info.spec.tsx index 16815bb462b..7001c73a8f5 100644 --- a/web/app/components/app/log/__tests__/model-info.spec.tsx +++ b/web/app/components/app/log/__tests__/model-info.spec.tsx @@ -1,15 +1,6 @@ import { fireEvent, render, screen } from '@testing-library/react' import ModelInfo from '../model-info' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ useTextGenerationCurrentProviderAndModelAndModelList: () => ({ currentModel: { @@ -153,7 +144,7 @@ describe('ModelInfo', () => { render() fireEvent.click(screen.getByRole('button')) - expect(screen.getByText('detail.modelParams')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)detail\.modelParams(?=$|:)/)).toBeInTheDocument() }) it('should render temperature parameter', () => { diff --git a/web/app/components/app/log/__tests__/var-panel.spec.tsx b/web/app/components/app/log/__tests__/var-panel.spec.tsx index b9c1fd4a20b..fff1a2e5286 100644 --- a/web/app/components/app/log/__tests__/var-panel.spec.tsx +++ b/web/app/components/app/log/__tests__/var-panel.spec.tsx @@ -1,15 +1,6 @@ import { act, fireEvent, render, screen } from '@testing-library/react' import VarPanel from '../var-panel' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/app/components/base/image-uploader/image-preview', () => ({ default: ({ url, title, onCancel }: { url: string, title: string, onCancel: () => void }) => (
@@ -31,7 +22,7 @@ describe('VarPanel', () => { it('should render variables section header', () => { render() - expect(screen.getByText('detail.variables')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)detail\.variables(?=$|:)/)).toBeInTheDocument() }) it('should render variable labels with braces', () => { @@ -77,7 +68,7 @@ describe('VarPanel', () => { it('should collapse when header is clicked', () => { render() - const header = screen.getByText('detail.variables').closest('div') + const header = screen.getByText(/(?:^|\.)detail\.variables(?=$|:)/).closest('div') fireEvent.click(header!) expect(screen.queryByText('John Doe')).not.toBeInTheDocument() @@ -87,7 +78,7 @@ describe('VarPanel', () => { it('should expand when clicked again', () => { render() - const header = screen.getByText('detail.variables').closest('div') + const header = screen.getByText(/(?:^|\.)detail\.variables(?=$|:)/).closest('div') // Collapse fireEvent.click(header!) @@ -101,7 +92,7 @@ describe('VarPanel', () => { it('should show arrow icon when collapsed', () => { const { container } = render() - const header = screen.getByText('detail.variables').closest('div') + const header = screen.getByText(/(?:^|\.)detail\.variables(?=$|:)/).closest('div') fireEvent.click(header!) // When collapsed, there should be SVG icons in the component @@ -122,7 +113,7 @@ describe('VarPanel', () => { it('should not render images section when message_files is empty', () => { render() - expect(screen.queryByText('detail.uploadImages')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)detail\.uploadImages(?=$|:)/)).not.toBeInTheDocument() }) it('should render images section when message_files has items', () => { @@ -133,7 +124,7 @@ describe('VarPanel', () => { render() - expect(screen.getByText('detail.uploadImages')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)detail\.uploadImages(?=$|:)/)).toBeInTheDocument() }) it('should render image thumbnails with correct background', () => { @@ -196,7 +187,7 @@ describe('VarPanel', () => { render() - expect(screen.getByText('detail.variables')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)detail\.variables(?=$|:)/)).toBeInTheDocument() }) }) diff --git a/web/app/components/app/overview/__tests__/app-card-sections.spec.tsx b/web/app/components/app/overview/__tests__/app-card-sections.spec.tsx index ed5556ce353..804fefe3fc4 100644 --- a/web/app/components/app/overview/__tests__/app-card-sections.spec.tsx +++ b/web/app/components/app/overview/__tests__/app-card-sections.spec.tsx @@ -7,16 +7,6 @@ import { withSelectorKey } from '@/test/i18n-mock' import { AppModeEnum } from '@/types/app' import { AppCardAccessControlSection, AppCardDialogs, AppCardOperations, AppCardUrlSection, createAppCardOperations, WorkflowLaunchDialog } from '../app-card-sections' -vi.mock('react-i18next', async () => { - const { withSelectorKey, withSelectorKeyProps } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - Trans: withSelectorKeyProps(({ i18nKey }: { i18nKey: string }) => {i18nKey}), - }) -}) - vi.mock('../settings', () => ({ default: () =>
, })) @@ -83,9 +73,9 @@ describe('app-card-sections', () => { />, ) - fireEvent.click(screen.getByText('publishApp.notSet')) + fireEvent.click(screen.getByText(/(?:^|\.)publishApp\.notSet(?=$|:)/)) - expect(screen.getByText('accessControlDialog.accessItems.specific')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)accessControlDialog\.accessItems\.specific(?=$|:)/)).toBeInTheDocument() expect(onClick).toHaveBeenCalledTimes(1) }) @@ -121,7 +111,7 @@ describe('app-card-sections', () => { expect(onLaunch).toHaveBeenCalledTimes(1) expect(onLaunchConfig).toHaveBeenCalledTimes(1) - expect(screen.getByText('overview.appInfo.launch')).not.toHaveAttribute('title') + expect(screen.getByText(/(?:^|\.)overview\.appInfo\.launch(?=$|:)/)).not.toHaveAttribute('title') expect(screen.getByRole('button', { name: /overview\.appInfo\.embedded\.entry/i })).toBeInTheDocument() }) @@ -180,7 +170,7 @@ describe('app-card-sections', () => { />, ) - expect(screen.getByRole('button', { name: 'overview.appInfo.customize.entry' })).toHaveAttribute('title', 'overview.appInfo.customize.entry') + expect(screen.getByRole('button', { name: /(?:^|\.)overview\.appInfo\.customize\.entry(?=$|:)/ })).toHaveAttribute('title', 'overview.appInfo.customize.entry') expect(AppModeEnum.CHAT).toBe('chat') }) @@ -254,7 +244,7 @@ describe('app-card-sections', () => { />, ) - expect(screen.getByText('overview.appInfo.workflowLaunchHiddenInputs.title')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)overview\.appInfo\.workflowLaunchHiddenInputs\.title(?=$|:)/)).toBeInTheDocument() fireEvent.submit(screen.getByRole('button', { name: /overview\.appInfo\.launch/i }).closest('form')!) expect(onSubmit).toHaveBeenCalled() }) diff --git a/web/app/components/app/overview/__tests__/app-card.spec.tsx b/web/app/components/app/overview/__tests__/app-card.spec.tsx index 5b8a11c4a31..3b579d8d9c4 100644 --- a/web/app/components/app/overview/__tests__/app-card.spec.tsx +++ b/web/app/components/app/overview/__tests__/app-card.spec.tsx @@ -9,6 +9,10 @@ import { AppACLPermission } from '@/utils/permission' import { basePath } from '@/utils/var' import AppCard from '../app-card' +vi.mock('@/context/i18n', () => ({ + useDocLink: () => (path: string) => `https://docs.example.com${path}`, +})) + const render = (ui: ReactElement) => renderWithSystemFeatures(ui, { systemFeatures: { webapp_auth: { enabled: true } }, }) @@ -23,20 +27,6 @@ let mockWorkflow: { graph?: { nodes?: Array<{ data?: { type?: string, variables? let mockAccessSubjects: { groups?: unknown[], members?: unknown[] } = { groups: [], members: [] } let mockAppDetail: AppDetailResponse | undefined -vi.mock('react-i18next', async () => { - const { withSelectorKey, withSelectorKeyProps } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - Trans: withSelectorKeyProps(({ i18nKey }: { i18nKey?: string }) => i18nKey ?? null), - }) -}) - -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.example.com${path}`, -})) - vi.mock('@/app/components/app/store', () => ({ useStore: (selector: (state: { appDetail: AppDetailResponse, setAppDetail: typeof mockSetAppDetail }) => unknown) => selector({ appDetail: mockAppDetail as AppDetailResponse, @@ -156,7 +146,7 @@ describe('AppCard', () => { />, ) - fireEvent.click(screen.getByText('overview.appInfo.launch')) + fireEvent.click(screen.getByText(/(?:^|\.)overview\.appInfo\.launch(?=$|:)/)) expect(mockWindowOpen).toHaveBeenCalledWith(`https://example.com${basePath}/chat/access-token`, '_blank') }) @@ -192,13 +182,13 @@ describe('AppCard', () => { />, ) - fireEvent.click(screen.getByText('overview.appInfo.launch')) + fireEvent.click(screen.getByText(/(?:^|\.)overview\.appInfo\.launch(?=$|:)/)) expect(mockWindowOpen).toHaveBeenCalledWith( `https://example.com${basePath}/workflow/access-token`, '_blank', ) - expect(screen.queryByText('overview.appInfo.workflowLaunchHiddenInputs.title')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)overview\.appInfo\.workflowLaunchHiddenInputs\.title(?=$|:)/)).not.toBeInTheDocument() }) it('should collect hidden workflow inputs from the config action before launching the workflow web app', async () => { @@ -232,14 +222,14 @@ describe('AppCard', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'operation.config' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.config(?=$|:)/ })) - expect(screen.getByText('overview.appInfo.workflowLaunchHiddenInputs.title')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)overview\.appInfo\.workflowLaunchHiddenInputs\.title(?=$|:)/)).toBeInTheDocument() fireEvent.change(screen.getByLabelText('Secret'), { target: { value: 'top-secret' }, }) - fireEvent.click(screen.getByRole('button', { name: 'overview.appInfo.launch' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)overview\.appInfo\.launch(?=$|:)/ })) await waitFor(() => { expect(mockWindowOpen).toHaveBeenCalledWith( @@ -280,13 +270,13 @@ describe('AppCard', () => { />, ) - fireEvent.click(screen.getByText('overview.appInfo.launch')) + fireEvent.click(screen.getByText(/(?:^|\.)overview\.appInfo\.launch(?=$|:)/)) expect(mockWindowOpen).toHaveBeenCalledWith( `https://example.com${basePath}/chat/access-token`, '_blank', ) - expect(screen.queryByText('overview.appInfo.workflowLaunchHiddenInputs.title')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)overview\.appInfo\.workflowLaunchHiddenInputs\.title(?=$|:)/)).not.toBeInTheDocument() }) it('should collect hidden chatflow inputs from the config action before launching the chat web app', async () => { @@ -320,14 +310,14 @@ describe('AppCard', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'operation.config' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.config(?=$|:)/ })) - expect(screen.getByText('overview.appInfo.workflowLaunchHiddenInputs.title')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)overview\.appInfo\.workflowLaunchHiddenInputs\.title(?=$|:)/)).toBeInTheDocument() fireEvent.change(screen.getByLabelText('Chat Secret'), { target: { value: 'chat-secret' }, }) - fireEvent.click(screen.getByRole('button', { name: 'overview.appInfo.launch' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)overview\.appInfo\.launch(?=$|:)/ })) await waitFor(() => { expect(mockWindowOpen).toHaveBeenCalledWith( @@ -345,7 +335,7 @@ describe('AppCard', () => { />, ) - expect(screen.getByText('publishApp.notSet')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)publishApp\.notSet(?=$|:)/)).toBeInTheDocument() }) it('should hide the access-control section when release permission is missing', () => { @@ -359,8 +349,8 @@ describe('AppCard', () => { />, ) - expect(screen.queryByText('publishApp.title')).not.toBeInTheDocument() - expect(screen.queryByText('publishApp.notSet')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)publishApp\.title(?=$|:)/)).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)publishApp\.notSet(?=$|:)/)).not.toBeInTheDocument() }) it('should hide the address and operation sections for unpublished workflows', () => { @@ -376,9 +366,9 @@ describe('AppCard', () => { />, ) - expect(screen.queryByText('overview.appInfo.accessibleAddress')).not.toBeInTheDocument() - expect(screen.queryByText('overview.appInfo.launch')).not.toBeInTheDocument() - expect(screen.getByText('overview.status.disable')).toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)overview\.appInfo\.accessibleAddress(?=$|:)/)).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)overview\.appInfo\.launch(?=$|:)/)).not.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)overview\.status\.disable(?=$|:)/)).toBeInTheDocument() }) it('should render api operations and navigate to the develop page', () => { @@ -395,7 +385,7 @@ describe('AppCard', () => { expect(screen.getByTestId('secret-key-button')).toHaveTextContent('app-1') - fireEvent.click(screen.getByText('overview.apiInfo.doc')) + fireEvent.click(screen.getByText(/(?:^|\.)overview\.apiInfo\.doc(?=$|:)/)) expect(mockPush).toHaveBeenCalledWith('/app/app-1/develop') }) @@ -408,13 +398,13 @@ describe('AppCard', () => { />, ) - fireEvent.click(screen.getByText('overview.appInfo.embedded.entry')) + fireEvent.click(screen.getByText(/(?:^|\.)overview\.appInfo\.embedded\.entry(?=$|:)/)) expect(screen.getByTestId('embedded-modal')).toBeInTheDocument() - fireEvent.click(screen.getByText('overview.appInfo.customize.entry')) + fireEvent.click(screen.getByText(/(?:^|\.)overview\.appInfo\.customize\.entry(?=$|:)/)) expect(screen.getByTestId('customize-modal')).toBeInTheDocument() - fireEvent.click(screen.getByText('overview.appInfo.settings.entry')) + fireEvent.click(screen.getByText(/(?:^|\.)overview\.appInfo\.settings\.entry(?=$|:)/)) expect(screen.getByTestId('settings-modal')).toBeInTheDocument() }) @@ -428,7 +418,7 @@ describe('AppCard', () => { ) const setQueryDataSpy = vi.spyOn(queryClient, 'setQueryData') - fireEvent.click(screen.getByText('publishApp.notSet')) + fireEvent.click(screen.getByText(/(?:^|\.)publishApp\.notSet(?=$|:)/)) expect(screen.getByTestId('access-control-modal')).toBeInTheDocument() fireEvent.click(screen.getByText('confirm-access-control')) @@ -461,8 +451,8 @@ describe('AppCard', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'overview.appInfo.enableTooltip.description' })) - fireEvent.click(screen.getByText('overview.appInfo.enableTooltip.learnMore')) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)overview\.appInfo\.enableTooltip\.description(?=$|:)/ })) + fireEvent.click(screen.getByText(/(?:^|\.)overview\.appInfo\.enableTooltip\.learnMore(?=$|:)/)) expect(mockWindowOpen).toHaveBeenCalledWith('https://docs.example.com/use-dify/nodes/user-input', '_blank') }) @@ -475,19 +465,19 @@ describe('AppCard', () => { />, ) - fireEvent.click(screen.getByText('overview.appInfo.embedded.entry')) + fireEvent.click(screen.getByText(/(?:^|\.)overview\.appInfo\.embedded\.entry(?=$|:)/)) fireEvent.click(screen.getByTestId('embedded-modal')) expect(screen.queryByTestId('embedded-modal')).not.toBeInTheDocument() - fireEvent.click(screen.getByText('overview.appInfo.customize.entry')) + fireEvent.click(screen.getByText(/(?:^|\.)overview\.appInfo\.customize\.entry(?=$|:)/)) fireEvent.click(screen.getByTestId('customize-modal')) expect(screen.queryByTestId('customize-modal')).not.toBeInTheDocument() - fireEvent.click(screen.getByText('overview.appInfo.settings.entry')) + fireEvent.click(screen.getByText(/(?:^|\.)overview\.appInfo\.settings\.entry(?=$|:)/)) fireEvent.click(screen.getByTestId('settings-modal')) expect(screen.queryByTestId('settings-modal')).not.toBeInTheDocument() - fireEvent.click(screen.getByText('publishApp.notSet')) + fireEvent.click(screen.getByText(/(?:^|\.)publishApp\.notSet(?=$|:)/)) fireEvent.click(screen.getByText('close-access-control')) expect(screen.queryByTestId('access-control-modal')).not.toBeInTheDocument() }) @@ -503,7 +493,7 @@ describe('AppCard', () => { />, ) - fireEvent.click(screen.getByText('publishApp.notSet')) + fireEvent.click(screen.getByText(/(?:^|\.)publishApp\.notSet(?=$|:)/)) fireEvent.click(screen.getByText('confirm-access-control')) await waitFor(() => { @@ -523,13 +513,13 @@ describe('AppCard', () => { const refreshButton = container.querySelector('[class*="refreshIcon"]')?.parentElement as HTMLElement fireEvent.click(refreshButton) - expect(screen.getByText('overview.appInfo.regenerateNotice')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)overview\.appInfo\.regenerateNotice(?=$|:)/)).toBeInTheDocument() - fireEvent.click(screen.getByRole('button', { name: 'operation.confirm' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.confirm(?=$|:)/ })) expect(mockOnGenerateCode).not.toHaveBeenCalled() return waitFor(() => { - expect(screen.queryByText('overview.appInfo.regenerateNotice')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)overview\.appInfo\.regenerateNotice(?=$|:)/)).not.toBeInTheDocument() }) }) @@ -545,7 +535,7 @@ describe('AppCard', () => { const refreshButton = container.querySelector('[class*="refreshIcon"]')?.parentElement as HTMLElement fireEvent.click(refreshButton) - fireEvent.click(screen.getByRole('button', { name: 'operation.confirm' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.confirm(?=$|:)/ })) await waitFor(() => { expect(mockOnGenerateCode).toHaveBeenCalledTimes(1) diff --git a/web/app/components/app/overview/__tests__/app-chart.spec.tsx b/web/app/components/app/overview/__tests__/app-chart.spec.tsx index d219a590b93..0385ed8bae4 100644 --- a/web/app/components/app/overview/__tests__/app-chart.spec.tsx +++ b/web/app/components/app/overview/__tests__/app-chart.spec.tsx @@ -2,16 +2,6 @@ import { render, screen } from '@testing-library/react' import Chart, { MessagesChart } from '../app-chart' const reactEChartsMock = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('echarts-for-react', () => ({ default: (props: { option: unknown, opts?: unknown }) => { reactEChartsMock(props) @@ -91,7 +81,7 @@ describe('app-chart', () => { />, ) - expect(screen.getByText('analysis.totalMessages.title'))!.toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)analysis\.totalMessages\.title(?=$|:)/))!.toBeInTheDocument() expect(screen.getByText('0'))!.toBeInTheDocument() expect(screen.getByTestId('echarts'))!.toBeInTheDocument() diff --git a/web/app/components/app/overview/__tests__/trigger-card.spec.tsx b/web/app/components/app/overview/__tests__/trigger-card.spec.tsx index 664698f52c8..8379186850f 100644 --- a/web/app/components/app/overview/__tests__/trigger-card.spec.tsx +++ b/web/app/components/app/overview/__tests__/trigger-card.spec.tsx @@ -4,19 +4,6 @@ import { AppModeEnum } from '@/types/app' import { AppACLPermission } from '@/utils/permission' import TriggerCard from '../trigger-card' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { count?: number }) => { - if (options?.count !== undefined) - return `${key} (${options.count})` - return key - }), - }), - }) -}) - vi.mock('@/context/i18n', () => ({ useDocLink: () => (path: string) => `https://docs.example.com${path}`, })) @@ -128,7 +115,7 @@ describe('TriggerCard', () => { render() - expect(screen.getByText('overview.triggerInfo.noTriggerAdded')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)overview\.triggerInfo\.noTriggerAdded(?=$|:)/)).toBeInTheDocument() }) it('should show trigger status description when no triggers', () => { @@ -136,7 +123,7 @@ describe('TriggerCard', () => { render() - expect(screen.getByText('overview.triggerInfo.triggerStatusDescription')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)overview\.triggerInfo\.triggerStatusDescription(?=$|:)/)).toBeInTheDocument() }) it('should show learn more link when no triggers', () => { @@ -144,7 +131,7 @@ describe('TriggerCard', () => { render() - const learnMoreLink = screen.getByText('overview.triggerInfo.learnAboutTriggers') + const learnMoreLink = screen.getByText(/(?:^|\.)overview\.triggerInfo\.learnAboutTriggers(?=$|:)/) expect(learnMoreLink).toBeInTheDocument() expect(learnMoreLink).toHaveAttribute('href', 'https://docs.example.com/use-dify/nodes/trigger/overview') }) @@ -173,7 +160,7 @@ describe('TriggerCard', () => { it('should show triggers count message', () => { render() - expect(screen.getByText('overview.triggerInfo.triggersAdded (2)')).toBeInTheDocument() + expect(screen.getByText(/overview\.triggerInfo\.triggersAdded.*2/)).toBeInTheDocument() }) it('should render trigger titles', () => { @@ -186,13 +173,13 @@ describe('TriggerCard', () => { it('should show running status for enabled triggers', () => { render() - expect(screen.getByText('overview.status.running')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)overview\.status\.running(?=$|:)/)).toBeInTheDocument() }) it('should show disable status for disabled triggers', () => { render() - expect(screen.getByText('overview.status.disable')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)overview\.status\.disable(?=$|:)/)).toBeInTheDocument() }) it('should render block icons for each trigger', () => { diff --git a/web/app/components/app/text-generate/item/__tests__/action-groups.spec.tsx b/web/app/components/app/text-generate/item/__tests__/action-groups.spec.tsx index dd0a9961d1d..aa6d8aa264a 100644 --- a/web/app/components/app/text-generate/item/__tests__/action-groups.spec.tsx +++ b/web/app/components/app/text-generate/item/__tests__/action-groups.spec.tsx @@ -9,16 +9,6 @@ const mockOnMoreLikeThis = vi.fn() const mockOnOpenLogModal = vi.fn() const mockOnRetry = vi.fn() const mockOnSave = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('copy-to-clipboard', () => ({ default: (...args: unknown[]) => mockCopy(...args), })) @@ -53,10 +43,10 @@ describe('GenerationActionGroups', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'operation.copy' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.copy(?=$|:)/ })) expect(mockCopy).toHaveBeenCalledWith('hello world') - expect(mockSuccess).toHaveBeenCalledWith('actionMsg.copySuccessfully') + expect(mockSuccess).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)actionMsg\.copySuccessfully(?=$|:)/)) }) it('should handle more-like-this and feedback actions', () => { @@ -80,9 +70,9 @@ describe('GenerationActionGroups', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'feature.moreLikeThis.title' })) - fireEvent.click(screen.getByRole('button', { name: 'operation.agree' })) - fireEvent.click(screen.getByRole('button', { name: 'operation.save' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)feature\.moreLikeThis\.title(?=$|:)/ })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.agree(?=$|:)/ })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.save(?=$|:)/ })) expect(mockOnMoreLikeThis).toHaveBeenCalledTimes(1) expect(mockOnFeedback).toHaveBeenCalledWith({ rating: 'like' }) @@ -106,7 +96,7 @@ describe('GenerationActionGroups', () => { />, ) - expect(screen.getByRole('button', { name: 'feature.moreLikeThis.title' })).toBeDisabled() + expect(screen.getByRole('button', { name: /(?:^|\.)feature\.moreLikeThis\.title(?=$|:)/ })).toBeDisabled() }) it('should stringify non-string content before copying', () => { @@ -125,7 +115,7 @@ describe('GenerationActionGroups', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'operation.copy' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.copy(?=$|:)/ })) expect(mockCopy).toHaveBeenCalledWith(JSON.stringify({ result: 'hello world' })) }) @@ -148,7 +138,7 @@ describe('GenerationActionGroups', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'generation.batchFailed.retry' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)generation\.batchFailed\.retry(?=$|:)/ })) expect(mockOnRetry).toHaveBeenCalledTimes(1) }) @@ -171,7 +161,7 @@ describe('GenerationActionGroups', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'operation.disagree' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.disagree(?=$|:)/ })) expect(mockOnFeedback).toHaveBeenCalledWith({ rating: 'dislike' }) rerender( @@ -191,7 +181,7 @@ describe('GenerationActionGroups', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'operation.cancelAgree' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.cancelAgree(?=$|:)/ })) expect(mockOnFeedback).toHaveBeenCalledWith({ rating: null }) rerender( @@ -211,7 +201,7 @@ describe('GenerationActionGroups', () => { />, ) - fireEvent.click(screen.getByRole('button', { name: 'operation.cancelDisagree' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.cancelDisagree(?=$|:)/ })) expect(mockOnFeedback).toHaveBeenCalledWith({ rating: null }) }) }) diff --git a/web/app/components/app/text-generate/item/__tests__/index.spec.tsx b/web/app/components/app/text-generate/item/__tests__/index.spec.tsx index f29eac7acf3..67a828720d0 100644 --- a/web/app/components/app/text-generate/item/__tests__/index.spec.tsx +++ b/web/app/components/app/text-generate/item/__tests__/index.spec.tsx @@ -11,16 +11,6 @@ const mockSetShowPromptLogModal = vi.fn() const mockSubmitHumanInputForm = vi.fn() const mockSubmitHumanInputFormWorkflow = vi.fn() const mockToastWarning = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/next/navigation', () => ({ useParams: () => ({ appId: 'app-1', @@ -121,7 +111,7 @@ describe('GenerationItem', () => { expect(screen.getByText('markdown:hello world')).toBeInTheDocument() await act(async () => { - fireEvent.click(screen.getByRole('button', { name: 'feature.moreLikeThis.title' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)feature\.moreLikeThis\.title(?=$|:)/ })) }) await waitFor(() => { @@ -130,7 +120,7 @@ describe('GenerationItem', () => { expect(mockFetchMoreLikeThis).toHaveBeenCalledWith('msg-1', AppSourceType.webApp, undefined) await act(async () => { - fireEvent.click(screen.getAllByRole('button', { name: 'operation.agree' }).at(-1)!) + fireEvent.click(screen.getAllByRole('button', { name: /(?:^|\.)operation\.agree(?=$|:)/ }).at(-1)!) }) expect(mockUpdateFeedback).toHaveBeenCalledWith({ @@ -158,7 +148,7 @@ describe('GenerationItem', () => { ) await act(async () => { - fireEvent.click(screen.getByRole('button', { name: 'operation.log' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)operation\.log(?=$|:)/ })) }) expect(mockFetchTextGenerationMessage).toHaveBeenCalledWith({ @@ -258,7 +248,7 @@ describe('GenerationItem', () => { ) await act(async () => { - fireEvent.click(screen.getByRole('button', { name: 'feature.moreLikeThis.title' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)feature\.moreLikeThis\.title(?=$|:)/ })) }) await waitFor(() => { @@ -316,10 +306,10 @@ describe('GenerationItem', () => { ) await act(async () => { - fireEvent.click(screen.getByRole('button', { name: 'feature.moreLikeThis.title' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)feature\.moreLikeThis\.title(?=$|:)/ })) }) - expect(mockToastWarning).toHaveBeenCalledWith('errorMessage.waitForResponse') + expect(mockToastWarning).toHaveBeenCalledWith(expect.stringMatching(/(?:^|\.)errorMessage\.waitForResponse(?=$|:)/)) expect(mockFetchMoreLikeThis).not.toHaveBeenCalled() }) }) diff --git a/web/app/components/app/text-generate/item/__tests__/workflow-body.spec.tsx b/web/app/components/app/text-generate/item/__tests__/workflow-body.spec.tsx index c68dce81986..391426a8443 100644 --- a/web/app/components/app/text-generate/item/__tests__/workflow-body.spec.tsx +++ b/web/app/components/app/text-generate/item/__tests__/workflow-body.spec.tsx @@ -4,16 +4,6 @@ import WorkflowBody from '../workflow-body' const mockSubmit = vi.fn() const mockSwitchTab = vi.fn() - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/app/components/base/chat/chat/answer/workflow-process', () => ({ default: () =>
workflow-process
, })) @@ -63,7 +53,7 @@ describe('WorkflowBody', () => { expect(screen.getByText('task-1-1')).toBeInTheDocument() expect(screen.getByText('result-tab:RESULT')).toBeInTheDocument() - fireEvent.click(screen.getByText('detail')) + fireEvent.click(screen.getByText(/(?:^|\.)detail(?=$|:)/)) expect(mockSwitchTab).toHaveBeenCalledWith('DETAIL') }) diff --git a/web/app/components/base/agent-log-modal/__tests__/iteration.spec.tsx b/web/app/components/base/agent-log-modal/__tests__/iteration.spec.tsx index 8266d2f4606..fdb1eaf470b 100644 --- a/web/app/components/base/agent-log-modal/__tests__/iteration.spec.tsx +++ b/web/app/components/base/agent-log-modal/__tests__/iteration.spec.tsx @@ -24,7 +24,7 @@ const mockIterationInfo: AgentIteration = { { status: 'success', tool_name: 'test_tool', - tool_label: { en: 'Test Tool' }, + tool_label: { 'en': 'Test Tool', 'en-US': 'Test Tool' }, tool_icon: null, }, ], diff --git a/web/app/components/base/chat/chat/answer/__tests__/reasoning-panel.spec.tsx b/web/app/components/base/chat/chat/answer/__tests__/reasoning-panel.spec.tsx index 8991bc2d132..4d3343ec5d6 100644 --- a/web/app/components/base/chat/chat/answer/__tests__/reasoning-panel.spec.tsx +++ b/web/app/components/base/chat/chat/answer/__tests__/reasoning-panel.spec.tsx @@ -3,20 +3,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import ReasoningPanel from '../reasoning-panel' // Mock react-i18next so the reused chat.thinking/chat.thought labels resolve. -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => { - const translations: Record = { - 'chat.thinking': 'Thinking...', - 'chat.thought': 'Thought', - } - return translations[key] || key - }), - }), - }) -}) // Mock the heavy Markdown renderer to a simple passthrough. vi.mock('@/app/components/base/markdown', () => ({ @@ -40,13 +26,13 @@ describe('ReasoningPanel', () => { it('shows the thinking state while not done', () => { render() - expect(screen.getByText(/Thinking\.\.\./)).toBeInTheDocument() + expect(screen.getByText(/chat\.thinking/)).toBeInTheDocument() expect(screen.getByText('let me think')).toBeInTheDocument() }) it('shows the thought state once done (answer started / terminal / history)', () => { render() - expect(screen.getByText(/Thought/)).toBeInTheDocument() + expect(screen.getByText(/chat\.thought/)).toBeInTheDocument() }) it('counts elapsed time up while thinking', () => { @@ -70,7 +56,7 @@ describe('ReasoningPanel', () => { act(() => { vi.advanceTimersByTime(1000) }) - expect(screen.getByText(/Thought\(0\.7s\)/)).toBeInTheDocument() + expect(screen.getByText(/chat\.thought\(0\.7s\)/)).toBeInTheDocument() }) it('concatenates reasoning from multiple LLM nodes', () => { diff --git a/web/app/components/base/features/new-feature-panel/moderation/__tests__/index.spec.tsx b/web/app/components/base/features/new-feature-panel/moderation/__tests__/index.spec.tsx index b5fb6c00435..b2fc193644f 100644 --- a/web/app/components/base/features/new-feature-panel/moderation/__tests__/index.spec.tsx +++ b/web/app/components/base/features/new-feature-panel/moderation/__tests__/index.spec.tsx @@ -14,11 +14,6 @@ vi.mock('@/context/modal-context', () => ({ setShowModerationSettingModal: mockSetShowModerationSettingModal, }), })) - -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - vi.mock('@/service/use-common', () => ({ useCodeBasedExtensions: () => ({ data: { data: mockCodeBasedExtensionData } }), })) diff --git a/web/app/components/base/markdown-blocks/__tests__/think-block.spec.tsx b/web/app/components/base/markdown-blocks/__tests__/think-block.spec.tsx index b4cbb420817..519d84478d0 100644 --- a/web/app/components/base/markdown-blocks/__tests__/think-block.spec.tsx +++ b/web/app/components/base/markdown-blocks/__tests__/think-block.spec.tsx @@ -4,20 +4,6 @@ import { ChatContextProvider } from '@/app/components/base/chat/chat/context-pro import ThinkBlock from '../think-block' // Mock react-i18next -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => { - const translations: Record = { - 'chat.thinking': 'Thinking...', - 'chat.thought': 'Thought', - } - return translations[key] || key - }), - }), - }) -}) // Helper to wrap component with ChatContextProvider const renderWithContext = ( @@ -73,7 +59,7 @@ describe('ThinkBlock', () => { true, ) - expect(screen.getByText(/Thinking\.\.\./)).toBeInTheDocument() + expect(screen.getByText(/chat\.thinking/)).toBeInTheDocument() expect(screen.getByText('Thinking content')).toBeInTheDocument() }) @@ -85,7 +71,7 @@ describe('ThinkBlock', () => { true, ) - expect(screen.getByText(/Thought/)).toBeInTheDocument() + expect(screen.getByText(/chat\.thought/)).toBeInTheDocument() }) }) @@ -133,7 +119,7 @@ describe('ThinkBlock', () => { ) // Verify initial thinking state - expect(screen.getByText(/Thinking\.\.\./)).toBeInTheDocument() + expect(screen.getByText(/chat\.thinking/)).toBeInTheDocument() // Advance timer act(() => { @@ -163,7 +149,7 @@ describe('ThinkBlock', () => { ) // Should now show "Thought" instead of "Thinking..." - expect(screen.getByText(/Thought/)).toBeInTheDocument() + expect(screen.getByText(/chat\.thought/)).toBeInTheDocument() }) it('should stop timer when isResponding is undefined (historical conversation outside active response)', () => { @@ -175,7 +161,7 @@ describe('ThinkBlock', () => { ) // Timer should be stopped immediately — isResponding undefined means not in active response - expect(screen.getByText(/Thought/)).toBeInTheDocument() + expect(screen.getByText(/chat\.thought/)).toBeInTheDocument() }) }) @@ -203,7 +189,7 @@ describe('ThinkBlock', () => { ) // Should show "Thought" since ENDTHINKFLAG is present - expect(screen.getByText(/Thought/)).toBeInTheDocument() + expect(screen.getByText(/chat\.thought/)).toBeInTheDocument() }) it('should detect ENDTHINKFLAG in array children', () => { @@ -214,7 +200,7 @@ describe('ThinkBlock', () => { true, ) - expect(screen.getByText(/Thought/)).toBeInTheDocument() + expect(screen.getByText(/chat\.thought/)).toBeInTheDocument() }) }) @@ -225,7 +211,7 @@ describe('ThinkBlock', () => { true, ) - expect(screen.getByText(/Thinking\.\.\./)).toBeInTheDocument() + expect(screen.getByText(/chat\.thinking/)).toBeInTheDocument() }) it('should handle null children gracefully', () => { @@ -236,7 +222,7 @@ describe('ThinkBlock', () => { true, ) - expect(screen.getByText(/Thinking\.\.\./)).toBeInTheDocument() + expect(screen.getByText(/chat\.thinking/)).toBeInTheDocument() }) }) }) diff --git a/web/app/components/datasets/__tests__/no-linked-apps-panel.spec.tsx b/web/app/components/datasets/__tests__/no-linked-apps-panel.spec.tsx index d6f0dfeabb9..02af6ad86af 100644 --- a/web/app/components/datasets/__tests__/no-linked-apps-panel.spec.tsx +++ b/web/app/components/datasets/__tests__/no-linked-apps-panel.spec.tsx @@ -2,11 +2,12 @@ import { cleanup, render, screen } from '@testing-library/react' import { afterEach, describe, expect, it, vi } from 'vitest' import NoLinkedAppsPanel from '../no-linked-apps-panel' -// Mock useDocLink vi.mock('@/context/i18n', () => ({ useDocLink: () => (path: string) => `https://docs.example.com${path}`, })) +// Mock useDocLink + afterEach(() => { cleanup() }) diff --git a/web/app/components/datasets/create/file-uploader/__tests__/index.spec.tsx b/web/app/components/datasets/create/file-uploader/__tests__/index.spec.tsx index c0635bebd1c..83a8f48f578 100644 --- a/web/app/components/datasets/create/file-uploader/__tests__/index.spec.tsx +++ b/web/app/components/datasets/create/file-uploader/__tests__/index.spec.tsx @@ -26,11 +26,6 @@ vi.mock('@/service/use-common', () => ({ data: { allowed_extensions: ['pdf', 'docx', 'txt'] }, }), })) - -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - vi.mock('@/i18n-config/language', () => ({ LanguagesSupported: ['en-US', 'zh-Hans'], })) diff --git a/web/app/components/datasets/create/file-uploader/hooks/__tests__/use-file-upload.spec.tsx b/web/app/components/datasets/create/file-uploader/hooks/__tests__/use-file-upload.spec.tsx index 74cd91c6d0a..ecf1e4feaa7 100644 --- a/web/app/components/datasets/create/file-uploader/hooks/__tests__/use-file-upload.spec.tsx +++ b/web/app/components/datasets/create/file-uploader/hooks/__tests__/use-file-upload.spec.tsx @@ -37,13 +37,6 @@ vi.mock('@/service/use-common', () => ({ useFileUploadConfig: () => ({ data: mockFileUploadConfig }), useFileSupportTypes: () => ({ data: mockSupportTypes }), })) - -// Mock i18n -// Mock locale -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - vi.mock('@/i18n-config/language', () => ({ LanguagesSupported: ['en-US', 'zh-Hans'], })) diff --git a/web/app/components/datasets/create/step-two/components/__tests__/indexing-mode-section.spec.tsx b/web/app/components/datasets/create/step-two/components/__tests__/indexing-mode-section.spec.tsx index 3931478f273..195c3f9e9ff 100644 --- a/web/app/components/datasets/create/step-two/components/__tests__/indexing-mode-section.spec.tsx +++ b/web/app/components/datasets/create/step-two/components/__tests__/indexing-mode-section.spec.tsx @@ -35,10 +35,6 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/model-selec ), })) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.dify.ai${path}`, -})) - const ns = 'datasetCreation' const createDefaultModel = (overrides?: Partial): DefaultModel => ({ diff --git a/web/app/components/datasets/documents/components/__tests__/documents-header.spec.tsx b/web/app/components/datasets/documents/components/__tests__/documents-header.spec.tsx index 664b05cbe35..9aceb5101fe 100644 --- a/web/app/components/datasets/documents/components/__tests__/documents-header.spec.tsx +++ b/web/app/components/datasets/documents/components/__tests__/documents-header.spec.tsx @@ -5,9 +5,6 @@ import { DataSourceType } from '@/models/datasets' import DocumentsHeader from '../documents-header' // Mock the context hooks -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.example.com${path}`, -})) // Mock child components that require API calls vi.mock('@/app/components/datasets/common/document-status-with-action/auto-disabled-document', () => ({ diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/hooks/__tests__/use-local-file-upload.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/hooks/__tests__/use-local-file-upload.spec.tsx index a09c23a8b6e..72a0d72db02 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/hooks/__tests__/use-local-file-upload.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/hooks/__tests__/use-local-file-upload.spec.tsx @@ -34,13 +34,6 @@ vi.mock('@/utils/format', () => ({ return parts[parts.length - 1] || '' }, })) - -// Mock react-i18next -// Mock locale context -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - // Mock i18n config vi.mock('@/i18n-config/language', () => ({ LanguagesSupported: ['en-US', 'zh-Hans'], diff --git a/web/app/components/datasets/documents/create-from-pipeline/steps/__tests__/step-three-content.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/steps/__tests__/step-three-content.spec.tsx index 8e80ab2878f..9a37d859db0 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/steps/__tests__/step-three-content.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/steps/__tests__/step-three-content.spec.tsx @@ -19,10 +19,6 @@ vi.mock('@/context/dataset-detail', () => ({ }), })) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.dify.ai${path}`, -})) - // Mock EmbeddingProcess component as it has complex dependencies vi.mock('../../processing/embedding-process', () => ({ default: ({ datasetId, batchId, documents }: { diff --git a/web/app/components/datasets/external-api/external-api-modal/__tests__/Form.spec.tsx b/web/app/components/datasets/external-api/external-api-modal/__tests__/Form.spec.tsx index fd23a183655..5b5bfac8ef8 100644 --- a/web/app/components/datasets/external-api/external-api-modal/__tests__/Form.spec.tsx +++ b/web/app/components/datasets/external-api/external-api-modal/__tests__/Form.spec.tsx @@ -3,11 +3,12 @@ import { fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import Form from '../Form' -// Mock context for i18n doc link vi.mock('@/context/i18n', () => ({ useDocLink: () => (path: string) => `https://docs.example.com${path}`, })) +// Mock context for i18n doc link + describe('Form', () => { const defaultFormSchemas: FormSchema[] = [ { diff --git a/web/app/components/datasets/external-api/external-api-panel/__tests__/index.spec.tsx b/web/app/components/datasets/external-api/external-api-panel/__tests__/index.spec.tsx index 573a9c16b73..aa32061addd 100644 --- a/web/app/components/datasets/external-api/external-api-panel/__tests__/index.spec.tsx +++ b/web/app/components/datasets/external-api/external-api-panel/__tests__/index.spec.tsx @@ -3,6 +3,10 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import ExternalAPIPanel from '../index' +vi.mock('@/context/i18n', () => ({ + useDocLink: () => (path: string) => `https://docs.example.com${path}`, +})) + // Mock external contexts (only mock context providers, not base components) const mockSetShowExternalKnowledgeAPIModal = vi.fn() const mockMutateExternalKnowledgeApis = vi.fn() @@ -23,10 +27,6 @@ vi.mock('@/context/external-knowledge-api-context', () => ({ }), })) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.example.com${path}`, -})) - // Mock the ExternalKnowledgeAPICard to avoid mocking its internal dependencies vi.mock('../../external-knowledge-api-card', () => ({ default: ({ api, canManageExternalKnowledgeApi }: { api: ExternalAPIItem, canManageExternalKnowledgeApi: boolean }) => ( diff --git a/web/app/components/datasets/external-knowledge-base/create/__tests__/InfoPanel.spec.tsx b/web/app/components/datasets/external-knowledge-base/create/__tests__/InfoPanel.spec.tsx index 9965565111d..91701e40538 100644 --- a/web/app/components/datasets/external-knowledge-base/create/__tests__/InfoPanel.spec.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/__tests__/InfoPanel.spec.tsx @@ -2,11 +2,12 @@ import { render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import InfoPanel from '../InfoPanel' -// Mock useDocLink from @/context/i18n vi.mock('@/context/i18n', () => ({ useDocLink: () => (path: string) => `https://docs.dify.ai${path}`, })) +// Mock useDocLink from @/context/i18n + describe('InfoPanel', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/datasets/extra-info/__tests__/statistics.spec.tsx b/web/app/components/datasets/extra-info/__tests__/statistics.spec.tsx index e88a4f928e3..042f6fae994 100644 --- a/web/app/components/datasets/extra-info/__tests__/statistics.spec.tsx +++ b/web/app/components/datasets/extra-info/__tests__/statistics.spec.tsx @@ -1,13 +1,10 @@ import type { RelatedApp, RelatedAppResponse } from '@/models/datasets' import { cleanup, render, screen } from '@testing-library/react' -import { afterEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it } from 'vitest' import { AppModeEnum } from '@/types/app' import Statistics from '../statistics' // Mock useDocLink -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.example.com${path}`, -})) afterEach(() => { cleanup() diff --git a/web/app/components/datasets/hit-testing/__tests__/modify-retrieval-modal.spec.tsx b/web/app/components/datasets/hit-testing/__tests__/modify-retrieval-modal.spec.tsx index 366732cbbcd..91d3a9923db 100644 --- a/web/app/components/datasets/hit-testing/__tests__/modify-retrieval-modal.spec.tsx +++ b/web/app/components/datasets/hit-testing/__tests__/modify-retrieval-modal.spec.tsx @@ -54,10 +54,6 @@ vi.mock('@/context/dataset-detail', () => ({ useDatasetDetailContextWithSelector: () => 'model-name', })) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.dify.ai${path}`, -})) - vi.mock('../../settings/utils', () => ({ checkShowMultiModalTip: () => false, })) diff --git a/web/app/components/datasets/settings/form/__tests__/index.spec.tsx b/web/app/components/datasets/settings/form/__tests__/index.spec.tsx index 3cc20431188..b79da32e9e2 100644 --- a/web/app/components/datasets/settings/form/__tests__/index.spec.tsx +++ b/web/app/components/datasets/settings/form/__tests__/index.spec.tsx @@ -254,10 +254,6 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ }, })) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.dify.ai${path}`, -})) - vi.mock('../components/indexing-section', () => ({ default: ({ currentDataset, diff --git a/web/app/components/datasets/settings/form/components/__tests__/indexing-section.spec.tsx b/web/app/components/datasets/settings/form/components/__tests__/indexing-section.spec.tsx index 2687dccaa38..a4c5b0365da 100644 --- a/web/app/components/datasets/settings/form/components/__tests__/indexing-section.spec.tsx +++ b/web/app/components/datasets/settings/form/components/__tests__/indexing-section.spec.tsx @@ -8,19 +8,6 @@ import { RETRIEVE_METHOD } from '@/types/app' import { IndexingType } from '../../../../create/step-two' import IndexingSection from '../indexing-section' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.dify.ai${path}`, -})) - vi.mock('@/app/components/base/divider', () => ({ default: ({ className }: { className?: string }) => (
@@ -276,17 +263,17 @@ describe('IndexingSection', () => { it('should render the chunk structure, index method, and retrieval sections for a standard dataset', () => { renderComponent() - expect(screen.getByText('form.chunkStructure.title')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)form\.chunkStructure\.title(?=$|:)/)).toBeInTheDocument() expect(screen.getByTestId('chunk-structure')).toHaveAttribute('data-mode', ChunkingMode.text) - expect(screen.getByText('form.indexMethod')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)form\.indexMethod(?=$|:)/)).toBeInTheDocument() expect(screen.getByTestId('index-method')).toBeInTheDocument() - expect(screen.getByText('form.retrievalSetting.title')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)form\.retrievalSetting\.title(?=$|:)/)).toBeInTheDocument() }) it('should render the embedding model selector when the index method is high quality', () => { renderComponent() - expect(screen.getByText('form.embeddingModel')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)form\.embeddingModel(?=$|:)/)).toBeInTheDocument() expect(screen.getByTestId('model-selector')).toHaveAttribute('data-model', 'text-embedding-ada-002') }) }) @@ -300,16 +287,16 @@ describe('IndexingSection', () => { }, }) - expect(screen.queryByText('form.chunkStructure.title')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)form\.chunkStructure\.title(?=$|:)/)).not.toBeInTheDocument() expect(screen.queryByTestId('chunk-structure')).not.toBeInTheDocument() }) it('should render the chunk structure learn more link and description', () => { renderComponent() - const learnMoreLink = screen.getByRole('link', { name: 'form.chunkStructure.learnMore' }) + const learnMoreLink = screen.getByRole('link', { name: /(?:^|\.)form\.chunkStructure\.learnMore(?=$|:)/ }) expect(learnMoreLink).toHaveAttribute('href', expect.stringContaining('chunking-and-cleaning-text')) - expect(screen.getByText('form.chunkStructure.description')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)form\.chunkStructure\.description(?=$|:)/)).toBeInTheDocument() }) }) @@ -322,22 +309,22 @@ describe('IndexingSection', () => { }, }) - expect(screen.queryByText('form.indexMethod')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)form\.indexMethod(?=$|:)/)).not.toBeInTheDocument() expect(screen.queryByTestId('index-method')).not.toBeInTheDocument() }) it('should render both index method options', () => { renderComponent() - expect(screen.getByRole('button', { name: 'stepTwo.qualified' })).toBeInTheDocument() - expect(screen.getByRole('button', { name: 'form.indexMethodEconomy' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: /(?:^|\.)stepTwo\.qualified(?=$|:)/ })).toBeInTheDocument() + expect(screen.getByRole('button', { name: /(?:^|\.)form\.indexMethodEconomy(?=$|:)/ })).toBeInTheDocument() }) it('should call setIndexMethod when the user selects a new index method', () => { const setIndexMethod = vi.fn() renderComponent({ setIndexMethod }) - fireEvent.click(screen.getByRole('button', { name: 'form.indexMethodEconomy' })) + fireEvent.click(screen.getByRole('button', { name: /(?:^|\.)form\.indexMethodEconomy(?=$|:)/ })) expect(setIndexMethod).toHaveBeenCalledWith(IndexingType.ECONOMICAL) }) @@ -350,7 +337,7 @@ describe('IndexingSection', () => { }, }) - expect(screen.getByText('form.upgradeHighQualityTip')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)form\.upgradeHighQualityTip(?=$|:)/)).toBeInTheDocument() }) it('should pass disabled state to the index method when embeddings are unavailable', () => { @@ -383,7 +370,7 @@ describe('IndexingSection', () => { it('should hide the embedding model selector for economy indexing', () => { renderComponent({ indexMethod: IndexingType.ECONOMICAL }) - expect(screen.queryByText('form.embeddingModel')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)form\.embeddingModel(?=$|:)/)).not.toBeInTheDocument() expect(screen.queryByTestId('model-selector')).not.toBeInTheDocument() }) @@ -425,9 +412,9 @@ describe('IndexingSection', () => { it('should render the retrieval learn more link', () => { renderComponent() - const learnMoreLink = screen.getByRole('link', { name: 'form.retrievalSetting.learnMore' }) + const learnMoreLink = screen.getByRole('link', { name: /(?:^|\.)form\.retrievalSetting\.learnMore(?=$|:)/ }) expect(learnMoreLink).toHaveAttribute('href', expect.stringContaining('setting-indexing-methods')) - expect(screen.getByText('form.retrievalSetting.description')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)form\.retrievalSetting\.description(?=$|:)/)).toBeInTheDocument() }) it('should render the high-quality retrieval config and propagate changes', () => { @@ -475,7 +462,7 @@ describe('IndexingSection', () => { }, }) - expect(screen.queryByText('form.retrievalSetting.title')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)form\.retrievalSetting\.title(?=$|:)/)).not.toBeInTheDocument() expect(screen.queryByTestId('retrieval-method-config')).not.toBeInTheDocument() expect(screen.queryByTestId('economical-retrieval-method-config')).not.toBeInTheDocument() }) @@ -497,7 +484,7 @@ describe('IndexingSection', () => { indexMethod: undefined, }) - expect(screen.queryByText('form.indexMethod')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)form\.indexMethod(?=$|:)/)).not.toBeInTheDocument() expect(screen.queryByTestId('index-method')).not.toBeInTheDocument() }) }) diff --git a/web/app/components/explore/app-list/__tests__/index.spec.tsx b/web/app/components/explore/app-list/__tests__/index.spec.tsx index 3288effb8d9..42484e3937c 100644 --- a/web/app/components/explore/app-list/__tests__/index.spec.tsx +++ b/web/app/components/explore/app-list/__tests__/index.spec.tsx @@ -315,9 +315,9 @@ type RenderOptions = { isCloudEdition?: boolean } -const localeInput = { query: { language: 'en' } } -const exploreAppListQueryKey = ['console', 'explore', 'apps', 'get', localeInput, 'en'] -const exploreBannersQueryKey = ['console', 'explore', 'banners', 'get', localeInput, 'en'] +const localeInput = { query: { language: 'en-US' } } +const exploreAppListQueryKey = ['console', 'explore', 'apps', 'get', localeInput, 'en-US'] +const exploreBannersQueryKey = ['console', 'explore', 'banners', 'get', localeInput, 'en-US'] const renderAppList = ( hasEditPermission = false, diff --git a/web/app/components/explore/banner/__tests__/banner.spec.tsx b/web/app/components/explore/banner/__tests__/banner.spec.tsx index a26959df12a..c1f9588de80 100644 --- a/web/app/components/explore/banner/__tests__/banner.spec.tsx +++ b/web/app/components/explore/banner/__tests__/banner.spec.tsx @@ -20,11 +20,6 @@ const setMockSelectedIndex = (index: number) => { mockSelectedIndex = index mockCarouselListeners.forEach(listener => listener()) } - -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - vi.mock('@/context/account-state', async (importOriginal) => { const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') @@ -65,6 +60,7 @@ vi.mock('react-i18next', async () => { const { withSelectorKey } = await import('@/test/i18n-mock') return ({ useTranslation: () => ({ + i18n: { language: 'en-US' }, t: withSelectorKey((key: string, opts?: Record) => { if (key === 'banner.greeting') return `Welcome back, ${opts?.name} 👋` diff --git a/web/app/components/explore/sidebar/no-apps/__tests__/index.spec.tsx b/web/app/components/explore/sidebar/no-apps/__tests__/index.spec.tsx index d4c37b8be58..3629315f358 100644 --- a/web/app/components/explore/sidebar/no-apps/__tests__/index.spec.tsx +++ b/web/app/components/explore/sidebar/no-apps/__tests__/index.spec.tsx @@ -2,12 +2,12 @@ import { render, screen } from '@testing-library/react' import { Theme } from '@/types/app' import NoApps from '../index' -let mockTheme = Theme.light - vi.mock('@/context/i18n', () => ({ useDocLink: () => (path: string) => `https://docs.dify.ai${path}`, })) +let mockTheme = Theme.light + vi.mock('@/hooks/use-theme', () => ({ default: () => ({ theme: mockTheme }), })) diff --git a/web/app/components/goto-anything/__tests__/index.spec.tsx b/web/app/components/goto-anything/__tests__/index.spec.tsx index 1eeed9a6a41..dcb1bbba427 100644 --- a/web/app/components/goto-anything/__tests__/index.spec.tsx +++ b/web/app/components/goto-anything/__tests__/index.spec.tsx @@ -66,11 +66,6 @@ let mockQueryResult = { data: [] as TestSearchResult[], isLoading: false, isErro vi.mock('@tanstack/react-query', () => ({ useQuery: () => mockQueryResult, })) - -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en_US', -})) - vi.mock('@/app/components/plugins/install-plugin/hooks/use-workspace-plugin-install-permission', () => ({ default: () => ({ canInstallPlugin: true, diff --git a/web/app/components/goto-anything/actions/commands/__tests__/create.spec.tsx b/web/app/components/goto-anything/actions/commands/__tests__/create.spec.tsx index 030adef1c92..e666dfaef83 100644 --- a/web/app/components/goto-anything/actions/commands/__tests__/create.spec.tsx +++ b/web/app/components/goto-anything/actions/commands/__tests__/create.spec.tsx @@ -7,16 +7,6 @@ vi.mock('@remixicon/react', () => ({ RiNodeTree: () => null, RiSparkling2Line: () => null, })) - -// search() localises its labels via getI18n(); echo the key back so the -// filtering/payload assertions stay deterministic without a real i18n init. -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - getI18n: () => ({ t: withSelectorKey((key: string) => key) }), - }) -}) - // We spy on the store at module scope so the `create.open` handler that // register() pushes into the command bus can be observed by the tests. const mockOpenGenerator = vi.fn() diff --git a/web/app/components/goto-anything/actions/commands/__tests__/language.spec.ts b/web/app/components/goto-anything/actions/commands/__tests__/language.spec.ts index ff12a2e84e6..23afc290ce9 100644 --- a/web/app/components/goto-anything/actions/commands/__tests__/language.spec.ts +++ b/web/app/components/goto-anything/actions/commands/__tests__/language.spec.ts @@ -2,16 +2,6 @@ import { registerCommands, unregisterCommands } from '../command-bus' import { languageCommand } from '../language' vi.mock('../command-bus') - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - getI18n: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/i18n-config/language', () => ({ languages: [ { value: 'en-US', name: 'English', supported: true }, diff --git a/web/app/components/goto-anything/actions/commands/__tests__/refine.spec.tsx b/web/app/components/goto-anything/actions/commands/__tests__/refine.spec.tsx index 11a1ee1864d..d1739aa1d65 100644 --- a/web/app/components/goto-anything/actions/commands/__tests__/refine.spec.tsx +++ b/web/app/components/goto-anything/actions/commands/__tests__/refine.spec.tsx @@ -5,16 +5,6 @@ import { refineCommand } from '../refine' vi.mock('@remixicon/react', () => ({ RiSparkling2Line: () => null, })) - -// search() localises its title/description via getI18n(); echo the key back -// so assertions stay deterministic without a real i18n init. -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - getI18n: () => ({ t: withSelectorKey((key: string) => key) }), - }) -}) - // Spy on the generator store so we can observe what /refine opens it with. const mockOpenGenerator = vi.fn() vi.mock('@/app/components/workflow/workflow-generator/store', () => ({ diff --git a/web/app/components/goto-anything/actions/commands/__tests__/slash.spec.tsx b/web/app/components/goto-anything/actions/commands/__tests__/slash.spec.tsx index b1df4956680..ea364c068e9 100644 --- a/web/app/components/goto-anything/actions/commands/__tests__/slash.spec.tsx +++ b/web/app/components/goto-anything/actions/commands/__tests__/slash.spec.tsx @@ -3,6 +3,11 @@ import { render } from '@testing-library/react' import { slashAction } from '../slash' import { SlashCommandProvider } from '../slash-provider' +vi.mock('react-i18next', async () => { + const { createReactI18nextLanguageMock } = await import('@/test/i18n-mock') + return createReactI18nextLanguageMock('ja') +}) + const { mockSetTheme, mockSetLocale, @@ -33,17 +38,6 @@ vi.mock('next-themes', () => ({ setTheme: mockSetTheme, }), })) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - getI18n: () => ({ - language: 'ja', - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/i18n-config', () => ({ setLocaleOnClient: mockSetLocale, })) diff --git a/web/app/components/goto-anything/actions/commands/__tests__/theme.spec.ts b/web/app/components/goto-anything/actions/commands/__tests__/theme.spec.ts index 9051b5e017e..ebf028b4c19 100644 --- a/web/app/components/goto-anything/actions/commands/__tests__/theme.spec.ts +++ b/web/app/components/goto-anything/actions/commands/__tests__/theme.spec.ts @@ -2,16 +2,6 @@ import { registerCommands, unregisterCommands } from '../command-bus' import { themeCommand } from '../theme' vi.mock('../command-bus') - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - getI18n: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - describe('themeCommand', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/app/components/goto-anything/hooks/__tests__/use-goto-anything-results.spec.ts b/web/app/components/goto-anything/hooks/__tests__/use-goto-anything-results.spec.ts index e5393a05a97..240185f77a0 100644 --- a/web/app/components/goto-anything/hooks/__tests__/use-goto-anything-results.spec.ts +++ b/web/app/components/goto-anything/hooks/__tests__/use-goto-anything-results.spec.ts @@ -22,11 +22,6 @@ vi.mock('@tanstack/react-query', () => ({ return mockQueryResult }, })) - -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en_US', -})) - const mockMatchAction = vi.fn() const mockSearchAnything = vi.fn() const mockGetRecentItems = vi.fn(() => [] as Array>) diff --git a/web/app/components/header/account-dropdown/__tests__/index.spec.tsx b/web/app/components/header/account-dropdown/__tests__/index.spec.tsx index 7e81d4abe72..9e5ba55dd05 100644 --- a/web/app/components/header/account-dropdown/__tests__/index.spec.tsx +++ b/web/app/components/header/account-dropdown/__tests__/index.spec.tsx @@ -101,10 +101,6 @@ vi.mock('@/next/navigation', async (importOriginal) => { } }) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.dify.ai${path}`, -})) - // Mock config and env const { mockConfig, mockEnv } = vi.hoisted(() => ({ mockConfig: { diff --git a/web/app/components/header/account-setting/data-source-page-new/__tests__/plugin-actions.spec.tsx b/web/app/components/header/account-setting/data-source-page-new/__tests__/plugin-actions.spec.tsx index e0270a06b07..15600bb2185 100644 --- a/web/app/components/header/account-setting/data-source-page-new/__tests__/plugin-actions.spec.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/__tests__/plugin-actions.spec.tsx @@ -73,11 +73,6 @@ vi.mock('@langgenius/dify-ui/tooltip', () => ({ TooltipTrigger: ({ render }: { render: ReactNode }) => <>{render}, TooltipContent: ({ children }: { children: ReactNode }) =>
{children}
, })) - -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - vi.mock('@/hooks/use-theme', () => ({ default: () => ({ theme: 'light' }), })) diff --git a/web/app/components/header/account-setting/members-page/invite-modal/__tests__/index.spec.tsx b/web/app/components/header/account-setting/members-page/invite-modal/__tests__/index.spec.tsx index 40f5c84a93e..ea8cf128845 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/__tests__/index.spec.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/__tests__/index.spec.tsx @@ -27,9 +27,7 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ error: mockToastError, }, })) -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) + vi.mock('react-multi-email', () => ({ ReactMultiEmail: ({ emails, onChange, getLabel }: { emails: string[], onChange: (emails: string[]) => void, getLabel: (email: string, index: number, removeEmail: (index: number) => void) => React.ReactNode }) => (
diff --git a/web/app/components/header/account-setting/model-provider-page/__tests__/hooks.spec.ts b/web/app/components/header/account-setting/model-provider-page/__tests__/hooks.spec.ts index 42d85e52aa2..0c5367ed459 100644 --- a/web/app/components/header/account-setting/model-provider-page/__tests__/hooks.spec.ts +++ b/web/app/components/header/account-setting/model-provider-page/__tests__/hooks.spec.ts @@ -37,6 +37,10 @@ import { useUpdateModelProviders, } from '../hooks' +vi.mock('@/context/i18n', () => ({ + useLocale: vi.fn(() => 'en-US'), +})) + // Mock dependencies vi.mock('@tanstack/react-query', () => ({ useQuery: vi.fn(), @@ -58,10 +62,6 @@ vi.mock('@/service/use-common', () => ({ }, })) -vi.mock('@/context/i18n', () => ({ - useLocale: vi.fn(() => 'en-US'), -})) - vi.mock('@/context/provider-context', () => ({ useProviderContext: vi.fn(() => ({ textGenerationModelList: [], diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx index 935292a599a..e83ec82a8a5 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/provider-card-actions.spec.tsx @@ -90,11 +90,6 @@ vi.mock('@/app/components/plugins/update-plugin/plugin-version-picker', () => ({
), })) - -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - vi.mock('@/hooks/use-theme', () => ({ default: () => ({ theme: 'light' }), })) diff --git a/web/app/components/integrations/__tests__/tool-provider-card.spec.tsx b/web/app/components/integrations/__tests__/tool-provider-card.spec.tsx index 529593083c8..d736e08add9 100644 --- a/web/app/components/integrations/__tests__/tool-provider-card.spec.tsx +++ b/web/app/components/integrations/__tests__/tool-provider-card.spec.tsx @@ -3,14 +3,11 @@ import { render, screen } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' import IntegrationsToolProviderCard from '../tool-provider-card' -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en_US', -})) - vi.mock('react-i18next', async () => { const { withSelectorKey } = await import('@/test/i18n-mock') return ({ useTranslation: () => ({ + i18n: { language: 'en-US' }, t: withSelectorKey((key: string, options?: { count?: number, ns?: string }) => options?.ns ? `${options.ns}.${key}${options.count ? `:${options.count}` : ''}` : key), }), }) @@ -56,7 +53,7 @@ describe('IntegrationsToolProviderCard', () => { it('does not show a source label for builtin tools', () => { render() - expect(screen.queryByText('plugin.from')).not.toBeInTheDocument() + expect(screen.queryByText(/(?:^|\.)plugin\.from(?=$|:)/)).not.toBeInTheDocument() }) it('does not show a built-in corner badge for marketplace plugin tools', () => { diff --git a/web/app/components/main-nav/__tests__/layout.spec.tsx b/web/app/components/main-nav/__tests__/layout.spec.tsx index 169c39de548..dbbc13f55c7 100644 --- a/web/app/components/main-nav/__tests__/layout.spec.tsx +++ b/web/app/components/main-nav/__tests__/layout.spec.tsx @@ -21,16 +21,6 @@ vi.mock('@/app/components/header', () => ({ vi.mock('@/app/components/header/header-wrapper', () => ({ default: ({ children }: { children: ReactNode }) =>
{children}
, })) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@tanstack/react-query', async (importOriginal) => { const actual = await importOriginal() return { @@ -130,7 +120,7 @@ describe('MainNavLayout', () => { it('renders skip navigation before the repeated main navigation', () => { const { container } = render(
content
) - const skipLink = screen.getByRole('link', { name: 'navigation.skipToMain' }) + const skipLink = screen.getByRole('link', { name: /(?:^|\.)navigation\.skipToMain(?=$|:)/ }) expect(skipLink).toHaveAttribute('href', '#main-content') expect(skipLink).toHaveClass('outline-hidden', 'focus-visible:ring-2', 'focus-visible:ring-state-accent-solid') @@ -140,7 +130,7 @@ describe('MainNavLayout', () => { it('moves focus to the main content when skip navigation is activated', () => { render(
content
) - const skipLink = screen.getByRole('link', { name: 'navigation.skipToMain' }) + const skipLink = screen.getByRole('link', { name: /(?:^|\.)navigation\.skipToMain(?=$|:)/ }) const main = screen.getByRole('main') fireEvent.click(skipLink) diff --git a/web/app/components/plugins/__tests__/provider-card.spec.tsx b/web/app/components/plugins/__tests__/provider-card.spec.tsx index 7a4758c3147..a8a49056f44 100644 --- a/web/app/components/plugins/__tests__/provider-card.spec.tsx +++ b/web/app/components/plugins/__tests__/provider-card.spec.tsx @@ -5,10 +5,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import ProviderCard from '../provider-card' import { PluginCategoryEnum } from '../types' -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - vi.mock('@/hooks/use-i18n', () => ({ useRenderI18nObject: () => (value: Record) => value['en-US'] || value.en_US, })) diff --git a/web/app/components/plugins/marketplace/__tests__/sticky-search-and-switch-wrapper.spec.tsx b/web/app/components/plugins/marketplace/__tests__/sticky-search-and-switch-wrapper.spec.tsx index b0d9916b90d..4dd742dc737 100644 --- a/web/app/components/plugins/marketplace/__tests__/sticky-search-and-switch-wrapper.spec.tsx +++ b/web/app/components/plugins/marketplace/__tests__/sticky-search-and-switch-wrapper.spec.tsx @@ -5,15 +5,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { createNuqsTestWrapper } from '@/test/nuqs-testing' import StickySearchAndSwitchWrapper from '../sticky-search-and-switch-wrapper' -vi.mock('#i18n', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - // Mock child components to isolate wrapper logic vi.mock('../plugin-type-switch', () => ({ default: () =>
PluginTypeSwitch
, diff --git a/web/app/components/plugins/marketplace/description/__tests__/index.spec.tsx b/web/app/components/plugins/marketplace/description/__tests__/index.spec.tsx index c12be3f3937..66e8d2b3803 100644 --- a/web/app/components/plugins/marketplace/description/__tests__/index.spec.tsx +++ b/web/app/components/plugins/marketplace/description/__tests__/index.spec.tsx @@ -5,6 +5,10 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { createNuqsTestWrapper } from '@/test/nuqs-testing' import Description from '../index' +vi.mock('@/context/i18n', () => ({ + useDocLink: () => (path: string) => `https://docs.example.com${path}`, +})) + // ================================ // Mock external dependencies // ================================ @@ -52,10 +56,6 @@ vi.mock('#i18n', async () => { }) }) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.example.com${path}`, -})) - const createWrapper = (searchParams = '') => { const { wrapper: NuqsWrapper } = createNuqsTestWrapper({ searchParams }) const Wrapper = ({ children }: { children: ReactNode }) => ( diff --git a/web/app/components/plugins/marketplace/list/__tests__/card-wrapper.spec.tsx b/web/app/components/plugins/marketplace/list/__tests__/card-wrapper.spec.tsx index be2b6cb0b29..69c854ce26b 100644 --- a/web/app/components/plugins/marketplace/list/__tests__/card-wrapper.spec.tsx +++ b/web/app/components/plugins/marketplace/list/__tests__/card-wrapper.spec.tsx @@ -6,16 +6,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { PluginCategoryEnum } from '@/app/components/plugins/types' import CardWrapper from '../card-wrapper' -vi.mock('#i18n', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - useLocale: () => 'en-US', - }) -}) - vi.mock('@/app/components/plugins/hooks', () => ({ useTags: () => ({ getTagLabel: (name: string) => `tag:${name}`, diff --git a/web/app/components/plugins/marketplace/list/__tests__/index.spec.tsx b/web/app/components/plugins/marketplace/list/__tests__/index.spec.tsx index 587532ba720..6f330b01c5f 100644 --- a/web/app/components/plugins/marketplace/list/__tests__/index.spec.tsx +++ b/web/app/components/plugins/marketplace/list/__tests__/index.spec.tsx @@ -53,11 +53,6 @@ vi.mock('../../state', () => ({ vi.mock('../../atoms', () => ({ useMarketplaceMoreClick: () => mockMoreClick, })) - -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - const mockTags = [ { name: 'search', label: 'Search' }, { name: 'image', label: 'Image' }, diff --git a/web/app/components/plugins/marketplace/list/__tests__/list-with-collection.spec.tsx b/web/app/components/plugins/marketplace/list/__tests__/list-with-collection.spec.tsx index f6752ebafa9..fd5c63cb930 100644 --- a/web/app/components/plugins/marketplace/list/__tests__/list-with-collection.spec.tsx +++ b/web/app/components/plugins/marketplace/list/__tests__/list-with-collection.spec.tsx @@ -5,17 +5,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import ListWithCollection from '../list-with-collection' const mockMoreClick = vi.fn() - -vi.mock('#i18n', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - useLocale: () => 'en-US', - }) -}) - vi.mock('../../atoms', () => ({ useMarketplaceMoreClick: () => mockMoreClick, })) diff --git a/web/app/components/plugins/marketplace/search-box/__tests__/search-box-wrapper.spec.tsx b/web/app/components/plugins/marketplace/search-box/__tests__/search-box-wrapper.spec.tsx index cc27307fdc0..1430f0f5e3b 100644 --- a/web/app/components/plugins/marketplace/search-box/__tests__/search-box-wrapper.spec.tsx +++ b/web/app/components/plugins/marketplace/search-box/__tests__/search-box-wrapper.spec.tsx @@ -5,16 +5,6 @@ import SearchBoxWrapper from '../search-box-wrapper' const mockHandleSearchPluginTextChange = vi.fn() const mockHandleFilterPluginTagsChange = vi.fn() const mockSearchBox = vi.fn() - -vi.mock('#i18n', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - vi.mock('../../atoms', () => ({ useSearchPluginText: () => ['plugin search', mockHandleSearchPluginTextChange], useFilterPluginTags: () => [['agent', 'rag'], mockHandleFilterPluginTagsChange], diff --git a/web/app/components/plugins/marketplace/search-box/trigger/__tests__/marketplace.spec.tsx b/web/app/components/plugins/marketplace/search-box/trigger/__tests__/marketplace.spec.tsx index 86ee2e0f11e..3e8c6f752d4 100644 --- a/web/app/components/plugins/marketplace/search-box/trigger/__tests__/marketplace.spec.tsx +++ b/web/app/components/plugins/marketplace/search-box/trigger/__tests__/marketplace.spec.tsx @@ -3,15 +3,6 @@ import { fireEvent, render, screen } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' import MarketplaceTrigger from '../marketplace' -vi.mock('#i18n', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - const tagsMap: Record = { agent: { name: 'agent', label: 'Agent' }, rag: { name: 'rag', label: 'RAG' }, 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 abf0ec57e95..67e25b4f9ca 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 @@ -78,12 +78,6 @@ vi.mock('ahooks', async () => { vi.mock('@/hooks/use-theme', () => ({ default: () => ({ theme: 'light' }), })) - -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en_US', - useLocale: () => 'en-US', -})) - // Global mock state for enable_marketplace seeded into the QueryClient via the local render helper. vi.mock('@/context/modal-context', () => ({ diff --git a/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-list.spec.tsx b/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-list.spec.tsx index d491de3ab1e..e01177a8b7a 100644 --- a/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-list.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-list.spec.tsx @@ -3,10 +3,6 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import EndpointList from '../endpoint-list' -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.example.com${path}`, -})) - vi.mock('@langgenius/dify-ui/cn', () => ({ cn: (...args: (string | undefined | false | null)[]) => args.filter(Boolean).join(' '), })) diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header/__tests__/index.spec.tsx index c663a91f5e2..053e5e7f27c 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header/__tests__/index.spec.tsx @@ -20,12 +20,6 @@ const render = (ui: ReactElement) => { systemFeatures: { enable_marketplace: true }, }) } - -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en_US', - useLocale: () => 'en-US', -})) - vi.mock('@/hooks/use-theme', () => ({ default: () => ({ theme: 'light' }), })) 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 79511071ade..90abb1bf88b 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 @@ -6,10 +6,6 @@ import { expectLoadingButton } from '@/test/button' import { PluginSource } from '../../../../types' import HeaderModals from '../header-modals' -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en_US', -})) - vi.mock('@/app/components/plugins/plugin-page/plugin-info', () => ({ default: ({ repository, release, packageName, onHide }: { repository: string diff --git a/web/app/components/plugins/plugin-page/__tests__/debug-info.spec.tsx b/web/app/components/plugins/plugin-page/__tests__/debug-info.spec.tsx index f3ea73a7c0e..73fda74bc7a 100644 --- a/web/app/components/plugins/plugin-page/__tests__/debug-info.spec.tsx +++ b/web/app/components/plugins/plugin-page/__tests__/debug-info.spec.tsx @@ -3,15 +3,15 @@ import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vitest' import DebugInfo from '../debug-info' +vi.mock('@/context/i18n', () => ({ + useDocLink: () => (path: string) => `https://docs.example.com${path}`, +})) + const mockDebugKey = vi.hoisted(() => ({ data: null as null | { key: string, host: string, port: number }, isLoading: false, })) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.example.com${path}`, -})) - vi.mock('@/service/use-plugins', () => ({ useDebugKey: () => mockDebugKey, })) diff --git a/web/app/components/plugins/plugin-page/__tests__/plugins-panel.spec.tsx b/web/app/components/plugins/plugin-page/__tests__/plugins-panel.spec.tsx index f9bd4927969..6d9fe201f2f 100644 --- a/web/app/components/plugins/plugin-page/__tests__/plugins-panel.spec.tsx +++ b/web/app/components/plugins/plugin-page/__tests__/plugins-panel.spec.tsx @@ -25,11 +25,6 @@ vi.mock('@tanstack/react-query', () => ({ queryOptions: (options: unknown) => options, useSuspenseQuery: () => ({ data: true }), })) - -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en_US', -})) - vi.mock('@/i18n-config', () => ({ renderI18nObject: (value: Record, locale: string) => value[locale] || '', })) diff --git a/web/app/components/plugins/plugin-page/plugin-tasks/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-page/plugin-tasks/__tests__/index.spec.tsx index 11ad7fb425a..4e1f7c0b224 100644 --- a/web/app/components/plugins/plugin-page/plugin-tasks/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-page/plugin-tasks/__tests__/index.spec.tsx @@ -21,11 +21,6 @@ vi.mock('@/app/components/plugins/install-plugin/base/use-get-icon', () => ({ getIconUrl: (icon: string) => `https://example.com/${icon}`, }), })) - -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en_US', -})) - // Helper to create mock plugin const createMockPlugin = (overrides: Partial = {}): PluginStatus => ({ plugin_unique_identifier: `plugin-${Math.random().toString(36).substr(2, 9)}`, diff --git a/web/app/components/plugins/plugin-page/plugin-tasks/components/__tests__/plugin-task-list.spec.tsx b/web/app/components/plugins/plugin-page/plugin-tasks/components/__tests__/plugin-task-list.spec.tsx index 813cb991a22..8c2fcddb975 100644 --- a/web/app/components/plugins/plugin-page/plugin-tasks/components/__tests__/plugin-task-list.spec.tsx +++ b/web/app/components/plugins/plugin-page/plugin-tasks/components/__tests__/plugin-task-list.spec.tsx @@ -16,11 +16,6 @@ vi.mock('@/app/components/plugins/install-plugin/install-from-marketplace', () = vi.mock('@/service/plugins', () => ({ fetchPluginInfoFromMarketPlace: vi.fn(), })) - -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en_US', -})) - const mockGetIconUrl = vi.fn((icon: string) => `https://icons/${icon}`) const createPlugin = (id: string, name: string, overrides: Partial = {}): PluginStatus => ({ diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx index abb87db43c0..4fc44ccc9a8 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/index.spec.tsx @@ -43,9 +43,6 @@ vi.mock('@/context/modal-context', () => ({ })) // Mock i18n context -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en-US', -})) vi.mock('react-i18next', async () => { const { withSelectorKey, withSelectorKeyProps } = await import('@/test/i18n-mock') diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/tool-item.spec.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/tool-item.spec.tsx index 6edfb799d08..0ed77f8d764 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/tool-item.spec.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/__tests__/tool-item.spec.tsx @@ -6,11 +6,6 @@ import ToolItem from '../tool-item' vi.mock('@/config', () => ({ MARKETPLACE_API_PREFIX: 'https://marketplace.example.com', })) - -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en_US', -})) - vi.mock('@/i18n-config', () => ({ renderI18nObject: (value: Record, language: string) => value[language], })) 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 063198d83f7..0813a3fa90a 100644 --- a/web/app/components/plugins/update-plugin/__tests__/index.spec.tsx +++ b/web/app/components/plugins/update-plugin/__tests__/index.spec.tsx @@ -17,9 +17,6 @@ import UpdatePlugin from '../index' import PluginVersionPicker from '../plugin-version-picker' // Mock useGetLanguage context -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en-US', -})) // Mock app context for useGetIcon diff --git a/web/app/components/rag-pipeline/hooks/__tests__/index.spec.ts b/web/app/components/rag-pipeline/hooks/__tests__/index.spec.ts index 31d2b604fb6..589450f4dd5 100644 --- a/web/app/components/rag-pipeline/hooks/__tests__/index.spec.ts +++ b/web/app/components/rag-pipeline/hooks/__tests__/index.spec.ts @@ -45,10 +45,6 @@ vi.mock('@/context/event-emitter', () => ({ }), })) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.dify.ai${path}`, -})) - vi.mock('@/app/components/workflow/constants', () => ({ DSL_EXPORT_CHECK: 'DSL_EXPORT_CHECK', WORKFLOW_DATA_UPDATE: 'WORKFLOW_DATA_UPDATE', diff --git a/web/app/components/share/text-generation/result/__tests__/index.spec.tsx b/web/app/components/share/text-generation/result/__tests__/index.spec.tsx index 9ece40bc24c..03e5c07b618 100644 --- a/web/app/components/share/text-generation/result/__tests__/index.spec.tsx +++ b/web/app/components/share/text-generation/result/__tests__/index.spec.tsx @@ -1,4 +1,3 @@ -import type { Namespace, SelectorParam } from 'i18next' import type { PromptConfig } from '@/models/debug' import type { SiteInfo } from '@/models/share' import type { IOtherOptions } from '@/service/base' @@ -24,10 +23,11 @@ const { vi.mock('i18next', async (importOriginal) => { const actual = await importOriginal() + const { createI18nextMock } = await import('@/test/i18n-mock') return { ...actual, - t: (selector: SelectorParam) => actual.keyFromSelector(selector), + ...createI18nextMock(), } }) diff --git a/web/app/components/tools/edit-custom-collection-modal/__tests__/test-api.spec.tsx b/web/app/components/tools/edit-custom-collection-modal/__tests__/test-api.spec.tsx index 4efe7e2dae6..6529b969d8a 100644 --- a/web/app/components/tools/edit-custom-collection-modal/__tests__/test-api.spec.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/__tests__/test-api.spec.tsx @@ -9,10 +9,6 @@ vi.mock('@/service/tools', () => ({ testAPIAvailable: vi.fn(), })) -vi.mock('@/context/i18n', () => ({ - useLocale: vi.fn(() => 'en-US'), -})) - const testAPIAvailableMock = vi.mocked(testAPIAvailable) describe('TestApi', () => { diff --git a/web/app/components/tools/marketplace/__tests__/index.spec.tsx b/web/app/components/tools/marketplace/__tests__/index.spec.tsx index 207cf72e974..2781267a06d 100644 --- a/web/app/components/tools/marketplace/__tests__/index.spec.tsx +++ b/web/app/components/tools/marketplace/__tests__/index.spec.tsx @@ -214,7 +214,7 @@ describe('Marketplace', () => { // Assert expect(showMarketplacePanel).toHaveBeenCalledTimes(1) expect(mockGetMarketplaceUrl).toHaveBeenCalledWith('', { - language: 'en', + language: 'en-US', q: 'vector', tags: 'tag-a,tag-b', theme: undefined, diff --git a/web/app/components/tools/provider/__tests__/detail.spec.tsx b/web/app/components/tools/provider/__tests__/detail.spec.tsx index c3301fca0df..849f0a74e97 100644 --- a/web/app/components/tools/provider/__tests__/detail.spec.tsx +++ b/web/app/components/tools/provider/__tests__/detail.spec.tsx @@ -4,10 +4,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { AuthType, CollectionType } from '../../types' import ProviderDetail from '../detail' -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - vi.mock('@/i18n-config/language', () => ({ getLanguage: () => 'en_US', })) diff --git a/web/app/components/tools/provider/__tests__/tool-item.spec.tsx b/web/app/components/tools/provider/__tests__/tool-item.spec.tsx index 3f85267bf1b..734b6f68441 100644 --- a/web/app/components/tools/provider/__tests__/tool-item.spec.tsx +++ b/web/app/components/tools/provider/__tests__/tool-item.spec.tsx @@ -3,11 +3,6 @@ import { fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import ToolItem from '../tool-item' -// Mock useLocale hook -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - // Mock getLanguage - returns key format used in TypeWithI18N (en_US, not en-US) vi.mock('@/i18n-config/language', () => ({ getLanguage: () => 'en_US', diff --git a/web/app/components/workflow/block-selector/__tests__/featured-triggers.spec.tsx b/web/app/components/workflow/block-selector/__tests__/featured-triggers.spec.tsx index 68b3110cb1c..30c6e98ae6c 100644 --- a/web/app/components/workflow/block-selector/__tests__/featured-triggers.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/featured-triggers.spec.tsx @@ -9,10 +9,6 @@ import { Theme } from '@/types/app' import { BlockEnum } from '../../types' import FeaturedTriggers from '../featured-triggers' -vi.mock('@/context/i18n', () => ({ - useGetLanguage: () => 'en_US', -})) - vi.mock('@/hooks/use-theme', () => ({ default: vi.fn(), })) diff --git a/web/app/components/workflow/comment/mention-input.spec.tsx b/web/app/components/workflow/comment/mention-input.spec.tsx index f22ed88767d..1fde6562211 100644 --- a/web/app/components/workflow/comment/mention-input.spec.tsx +++ b/web/app/components/workflow/comment/mention-input.spec.tsx @@ -19,16 +19,6 @@ const mentionStoreState = vi.hoisted(() => ({ mentionStoreState.mentionableUsersCache[appId] = users }, })) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - vi.mock('@/next/navigation', () => ({ useParams: () => ({ appId: 'app-1' }), })) diff --git a/web/app/components/workflow/comment/thread.spec.tsx b/web/app/components/workflow/comment/thread.spec.tsx index c4dffa7f06b..3cf742df579 100644 --- a/web/app/components/workflow/comment/thread.spec.tsx +++ b/web/app/components/workflow/comment/thread.spec.tsx @@ -21,16 +21,6 @@ const storeState = vi.hoisted(() => ({ } as Record>, setCommentPreviewHovering: (...args: unknown[]) => mockSetCommentPreviewHovering(...args), })) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - vi.mock('@/next/navigation', () => ({ useParams: () => ({ appId: 'app-1' }), })) diff --git a/web/app/components/workflow/hooks/__tests__/use-nodes-meta-data.spec.tsx b/web/app/components/workflow/hooks/__tests__/use-nodes-meta-data.spec.tsx index 9dffa46cb2e..edb8affac40 100644 --- a/web/app/components/workflow/hooks/__tests__/use-nodes-meta-data.spec.tsx +++ b/web/app/components/workflow/hooks/__tests__/use-nodes-meta-data.spec.tsx @@ -4,14 +4,14 @@ import { renderWorkflowHook } from '../../__tests__/workflow-test-env' import { BlockEnum } from '../../types' import { useNodeMetaData, useNodesMetaData } from '../use-nodes-meta-data' -const buildInToolsState = vi.hoisted(() => [] as Array<{ id: string, author: string, description: Record }>) -const customToolsState = vi.hoisted(() => [] as Array<{ id: string, author: string, description: Record }>) -const workflowToolsState = vi.hoisted(() => [] as Array<{ id: string, author: string, description: Record }>) - vi.mock('@/context/i18n', () => ({ useGetLanguage: () => 'en-US', })) +const buildInToolsState = vi.hoisted(() => [] as Array<{ id: string, author: string, description: Record }>) +const customToolsState = vi.hoisted(() => [] as Array<{ id: string, author: string, description: Record }>) +const workflowToolsState = vi.hoisted(() => [] as Array<{ id: string, author: string, description: Record }>) + vi.mock('@/service/use-tools', () => ({ useAllBuiltInTools: () => ({ data: buildInToolsState }), useAllCustomTools: () => ({ data: customToolsState }), diff --git a/web/app/components/workflow/hooks/__tests__/use-workflow-history.spec.tsx b/web/app/components/workflow/hooks/__tests__/use-workflow-history.spec.tsx index c733a14943d..37c1b74d207 100644 --- a/web/app/components/workflow/hooks/__tests__/use-workflow-history.spec.tsx +++ b/web/app/components/workflow/hooks/__tests__/use-workflow-history.spec.tsx @@ -25,18 +25,6 @@ vi.mock('reactflow', async () => { }), } }) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - const actual = await vi.importActual('react-i18next') - return { - ...actual, - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - } -}) - const nodes: Node[] = [{ id: 'node-1', type: 'custom', @@ -106,7 +94,7 @@ describe('useWorkflowHistory', () => { }, }) - expect(result.current.getHistoryLabel(WorkflowHistoryEvent.NodeDelete)).toBe('changeHistory.nodeDelete') + expect(result.current.getHistoryLabel(WorkflowHistoryEvent.NodeDelete)).toEqual(expect.stringMatching(/(?:^|\.)changeHistory\.nodeDelete(?=$|:)/)) expect(result.current.getHistoryLabel('Unknown' as keyof typeof WorkflowHistoryEvent)).toBe('Unknown Event') }) diff --git a/web/app/components/workflow/nodes/_base/components/__tests__/agent-strategy-selector.spec.tsx b/web/app/components/workflow/nodes/_base/components/__tests__/agent-strategy-selector.spec.tsx index b61eaccfb1b..bd0ac7b7b5b 100644 --- a/web/app/components/workflow/nodes/_base/components/__tests__/agent-strategy-selector.spec.tsx +++ b/web/app/components/workflow/nodes/_base/components/__tests__/agent-strategy-selector.spec.tsx @@ -187,16 +187,6 @@ vi.mock('@/next/link', () => ({ className?: string }) => {children}, })) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@langgenius/dify-ui/popover', async () => { const React = await import('react') const PopoverContext = React.createContext({ @@ -321,7 +311,7 @@ describe('AgentStrategySelector', () => { expect(screen.getByTestId('plugin-list')).toHaveTextContent(':market-agent') await user.type( - screen.getByRole('textbox', { name: 'nodes.agent.strategy.searchPlaceholder' }), + screen.getByRole('textbox', { name: /(?:^|\.)nodes\.agent\.strategy\.searchPlaceholder(?=$|:)/ }), 'alp', ) @@ -385,8 +375,8 @@ describe('AgentStrategySelector', () => { />, ) - expect(screen.getByText('nodes.agent.pluginNotInstalled')).toBeInTheDocument() - expect(screen.getByText('nodes.agent.pluginNotInstalledDesc')).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)nodes\.agent\.pluginNotInstalled(?=$|:)/)).toBeInTheDocument() + expect(screen.getByText(/(?:^|\.)nodes\.agent\.pluginNotInstalledDesc(?=$|:)/)).toBeInTheDocument() }) it('renders install and switch-version actions for marketplace strategies', async () => { diff --git a/web/app/components/workflow/nodes/_base/components/next-step/__tests__/operator.spec.tsx b/web/app/components/workflow/nodes/_base/components/next-step/__tests__/operator.spec.tsx index 25ab57bb931..2bdb4ce170a 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/__tests__/operator.spec.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/__tests__/operator.spec.tsx @@ -12,15 +12,6 @@ import { import { BlockEnum } from '@/app/components/workflow/types' import Operator from '../operator' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - vi.mock('@langgenius/dify-ui/dropdown-menu', async () => { const React = await import('react') const DropdownMenuContext = React.createContext<{ open: boolean, setOpen: (open: boolean) => void } | null>(null) diff --git a/web/app/components/workflow/nodes/_base/hooks/__tests__/use-one-step-run.spec.ts b/web/app/components/workflow/nodes/_base/hooks/__tests__/use-one-step-run.spec.ts index 25bb3ce5b8d..f42efaa3b1f 100644 --- a/web/app/components/workflow/nodes/_base/hooks/__tests__/use-one-step-run.spec.ts +++ b/web/app/components/workflow/nodes/_base/hooks/__tests__/use-one-step-run.spec.ts @@ -20,16 +20,6 @@ const mockWorkflowState = { setListeningTriggerIsAll: vi.fn(), setShowVariableInspectPanel: vi.fn(), } - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@langgenius/dify-ui/toast', () => ({ toast: { error: vi.fn(), diff --git a/web/app/components/workflow/nodes/human-input/components/__tests__/single-run-form.spec.tsx b/web/app/components/workflow/nodes/human-input/components/__tests__/single-run-form.spec.tsx index 8a8e0ef2501..0763096bbd3 100644 --- a/web/app/components/workflow/nodes/human-input/components/__tests__/single-run-form.spec.tsx +++ b/web/app/components/workflow/nodes/human-input/components/__tests__/single-run-form.spec.tsx @@ -7,15 +7,6 @@ import { InputVarType } from '@/app/components/workflow/types' import { UserActionButtonType } from '../../types' import SingleRunForm from '../single-run-form' -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/app/components/base/chat/chat/answer/human-input-content/content-item', () => ({ __esModule: true, default: ({ @@ -110,7 +101,7 @@ describe('SingleRunForm', () => { />, ) - await user.click(screen.getByRole('button', { name: 'nodes.humanInput.singleRun.back' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)nodes\.humanInput\.singleRun\.back(?=$|:)/ })) expect(handleBack).toHaveBeenCalledTimes(1) }) diff --git a/web/app/components/workflow/nodes/iteration/__tests__/use-interactions.spec.tsx b/web/app/components/workflow/nodes/iteration/__tests__/use-interactions.spec.tsx index 4e444c981b9..e4fa09ab928 100644 --- a/web/app/components/workflow/nodes/iteration/__tests__/use-interactions.spec.tsx +++ b/web/app/components/workflow/nodes/iteration/__tests__/use-interactions.spec.tsx @@ -24,16 +24,6 @@ vi.mock('reactflow', async () => { }), } }) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('@/app/components/workflow/hooks', () => ({ useNodesMetaData: () => ({ nodesMap: { diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/__tests__/json-importer.spec.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/__tests__/json-importer.spec.tsx index bd49ef128f3..769f7c97901 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/__tests__/json-importer.spec.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/__tests__/json-importer.spec.tsx @@ -11,16 +11,6 @@ const visualEditorState = { advancedEditing: false, isAddingNewField: false, } - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - vi.mock('../visual-editor/context', () => ({ useMittContext: () => ({ emit: mockEmit, @@ -151,7 +141,7 @@ describe('JsonImporter', () => { expect(mockUpdateBtnWidth).toHaveBeenCalledWith(88) - await user.click(screen.getByRole('button', { name: 'nodes.llm.jsonSchema.import' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)nodes\.llm\.jsonSchema\.import(?=$|:)/ })) expect(screen.getByTestId('popover-content')).toBeInTheDocument() expect(mockEmit).not.toHaveBeenCalled() @@ -168,7 +158,7 @@ describe('JsonImporter', () => { />, ) - await user.click(screen.getByRole('button', { name: 'nodes.llm.jsonSchema.import' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)nodes\.llm\.jsonSchema\.import(?=$|:)/ })) expect(mockEmit).toHaveBeenCalledWith('quitEditing', {}) }) @@ -183,9 +173,9 @@ describe('JsonImporter', () => { />, ) - await user.click(screen.getByRole('button', { name: 'nodes.llm.jsonSchema.import' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)nodes\.llm\.jsonSchema\.import(?=$|:)/ })) fireEvent.change(screen.getByLabelText('json-editor'), { target: { value: '[]' } }) - await user.click(screen.getByRole('button', { name: 'operation.submit' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)operation\.submit(?=$|:)/ })) expect(screen.getByTestId('error-message')).toHaveTextContent('Root must be an object, not an array or primitive value.') expect(mockOnSubmit).not.toHaveBeenCalled() @@ -202,9 +192,9 @@ describe('JsonImporter', () => { />, ) - await user.click(screen.getByRole('button', { name: 'nodes.llm.jsonSchema.import' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)nodes\.llm\.jsonSchema\.import(?=$|:)/ })) fireEvent.change(screen.getByLabelText('json-editor'), { target: { value: '{"foo":{"bar":1}}' } }) - await user.click(screen.getByRole('button', { name: 'operation.submit' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)operation\.submit(?=$|:)/ })) expect(screen.getByTestId('error-message')).toHaveTextContent(`Schema exceeds maximum depth of ${JSON_SCHEMA_MAX_DEPTH}.`) expect(mockOnSubmit).not.toHaveBeenCalled() @@ -223,9 +213,9 @@ describe('JsonImporter', () => { />, ) - await user.click(screen.getByRole('button', { name: 'nodes.llm.jsonSchema.import' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)nodes\.llm\.jsonSchema\.import(?=$|:)/ })) fireEvent.change(screen.getByLabelText('json-editor'), { target: { value: '{"foo":1}' } }) - await user.click(screen.getByRole('button', { name: 'operation.submit' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)operation\.submit(?=$|:)/ })) expect(screen.getByTestId('error-message')).toHaveTextContent('Malformed JSON payload') expect(mockOnSubmit).not.toHaveBeenCalled() @@ -244,9 +234,9 @@ describe('JsonImporter', () => { />, ) - await user.click(screen.getByRole('button', { name: 'nodes.llm.jsonSchema.import' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)nodes\.llm\.jsonSchema\.import(?=$|:)/ })) fireEvent.change(screen.getByLabelText('json-editor'), { target: { value: '{"foo":1}' } }) - await user.click(screen.getByRole('button', { name: 'operation.submit' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)operation\.submit(?=$|:)/ })) expect(screen.getByTestId('error-message')).toHaveTextContent('Invalid JSON') expect(mockOnSubmit).not.toHaveBeenCalled() @@ -264,15 +254,15 @@ describe('JsonImporter', () => { />, ) - await user.click(screen.getByRole('button', { name: 'nodes.llm.jsonSchema.import' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)nodes\.llm\.jsonSchema\.import(?=$|:)/ })) fireEvent.change(screen.getByLabelText('json-editor'), { target: { value: '{"foo":"bar"}' } }) - await user.click(screen.getByRole('button', { name: 'operation.submit' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)operation\.submit(?=$|:)/ })) expect(mockOnSubmit).toHaveBeenCalledWith({ foo: 'bar' }) expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() - await user.click(screen.getByRole('button', { name: 'nodes.llm.jsonSchema.import' })) - await user.click(screen.getByRole('button', { name: 'operation.cancel' })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)nodes\.llm\.jsonSchema\.import(?=$|:)/ })) + await user.click(screen.getByRole('button', { name: /(?:^|\.)operation\.cancel(?=$|:)/ })) expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument() }) diff --git a/web/app/components/workflow/operator/__tests__/more-actions.spec.tsx b/web/app/components/workflow/operator/__tests__/more-actions.spec.tsx index cd8cf43bbee..0cf02774d70 100644 --- a/web/app/components/workflow/operator/__tests__/more-actions.spec.tsx +++ b/web/app/components/workflow/operator/__tests__/more-actions.spec.tsx @@ -19,16 +19,6 @@ const { appName: 'Demo App', }, })) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - vi.mock('@langgenius/dify-ui/dropdown-menu', async () => { const React = await import('react') const DropdownMenuContext = React.createContext<{ open: boolean, setOpen: (open: boolean) => void } | null>(null) diff --git a/web/app/components/workflow/panel/__tests__/human-input-form-list.spec.tsx b/web/app/components/workflow/panel/__tests__/human-input-form-list.spec.tsx index 9b0e1cff6b9..466c4c207c9 100644 --- a/web/app/components/workflow/panel/__tests__/human-input-form-list.spec.tsx +++ b/web/app/components/workflow/panel/__tests__/human-input-form-list.spec.tsx @@ -21,11 +21,6 @@ vi.mock('reactflow', () => ({ }), }), })) - -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - const createFormData = (overrides: Partial = {}): HumanInputFormData => ({ form_id: 'form-1', node_id: 'human-node-1', diff --git a/web/app/components/workflow/panel/comments-panel/__tests__/index.spec.tsx b/web/app/components/workflow/panel/comments-panel/__tests__/index.spec.tsx index d1f1a2c5344..d225b0e8594 100644 --- a/web/app/components/workflow/panel/comments-panel/__tests__/index.spec.tsx +++ b/web/app/components/workflow/panel/comments-panel/__tests__/index.spec.tsx @@ -54,16 +54,6 @@ const storeState = { activeCommentId: null as string | null, showResolvedComments: true, } - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key), - }), - }) -}) - vi.mock('@/next/navigation', () => ({ useParams: () => ({ appId: 'app-1' }), })) diff --git a/web/app/education-apply/__tests__/search-input.spec.tsx b/web/app/education-apply/__tests__/search-input.spec.tsx index 6803d8cfd45..38ccb2c9a55 100644 --- a/web/app/education-apply/__tests__/search-input.spec.tsx +++ b/web/app/education-apply/__tests__/search-input.spec.tsx @@ -15,16 +15,6 @@ const educationMocks = vi.hoisted(() => ({ vi.mock('../hooks', () => ({ useEducation: () => educationMocks, })) - -vi.mock('react-i18next', async () => { - const { withSelectorKey } = await import('@/test/i18n-mock') - return ({ - useTranslation: () => ({ - t: withSelectorKey((key: string) => key), - }), - }) -}) - const ControlledSearchInput = () => { const [value, setValue] = useState('') return @@ -43,7 +33,7 @@ describe('education-apply/search-input', () => { render() - const input = screen.getByPlaceholderText('form.schoolName.placeholder') as HTMLInputElement + const input = screen.getByPlaceholderText(/(?:^|\.)form\.schoolName\.placeholder(?=$|:)/) as HTMLInputElement expect(input.type).toBe('text') await user.type(input, 'Alpha') @@ -61,7 +51,7 @@ describe('education-apply/search-input', () => { render() - await user.type(screen.getByPlaceholderText('form.schoolName.placeholder'), 'A') + await user.type(screen.getByPlaceholderText(/(?:^|\.)form\.schoolName\.placeholder(?=$|:)/), 'A') expect(screen.getByText('Alpha University')).toBeInTheDocument() @@ -77,7 +67,7 @@ describe('education-apply/search-input', () => { render() - await user.type(screen.getByPlaceholderText('form.schoolName.placeholder'), 'A') + await user.type(screen.getByPlaceholderText(/(?:^|\.)form\.schoolName\.placeholder(?=$|:)/), 'A') const scrollContainer = screen.getByText('Alpha University').parentElement as HTMLDivElement Object.defineProperties(scrollContainer, { diff --git a/web/app/install/installForm.spec.tsx b/web/app/install/installForm.spec.tsx index d23c9c8d9ab..a3e180b2140 100644 --- a/web/app/install/installForm.spec.tsx +++ b/web/app/install/installForm.spec.tsx @@ -81,7 +81,7 @@ describe('InstallForm', () => { email: 'admin@example.com', name: 'Admin', password: 'Password123', - language: 'en', + language: 'en-US', }, }) }) diff --git a/web/app/signin/__tests__/_header.spec.tsx b/web/app/signin/__tests__/_header.spec.tsx index a407ff830f2..0ed0904e4c7 100644 --- a/web/app/signin/__tests__/_header.spec.tsx +++ b/web/app/signin/__tests__/_header.spec.tsx @@ -6,11 +6,6 @@ import Header from '../_header' vi.mock('@tanstack/react-query', () => ({ useSuspenseQuery: vi.fn(), })) - -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - vi.mock('@/i18n-config', () => ({ setLocaleOnClient: vi.fn(), })) diff --git a/web/features/agent-v2/agent-detail/logs/__tests__/page.spec.tsx b/web/features/agent-v2/agent-detail/logs/__tests__/page.spec.tsx index 344535420f0..21f55d0abe5 100644 --- a/web/features/agent-v2/agent-detail/logs/__tests__/page.spec.tsx +++ b/web/features/agent-v2/agent-detail/logs/__tests__/page.spec.tsx @@ -31,10 +31,6 @@ const mocks = vi.hoisted(() => ({ })), })) -vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path: string) => `https://docs.example.com${path}`, -})) - vi.mock('@/hooks/use-timestamp', () => ({ default: () => ({ formatTime: (value: number) => `formatted-${value}`, diff --git a/web/hooks/use-format-time-from-now.spec.ts b/web/hooks/use-format-time-from-now.spec.ts index 94eb08de904..fc5c5350290 100644 --- a/web/hooks/use-format-time-from-now.spec.ts +++ b/web/hooks/use-format-time-from-now.spec.ts @@ -18,11 +18,12 @@ import { useLocale } from '@/context/i18n' import { useFormatTimeFromNow } from './use-format-time-from-now' -// Mock the i18n context vi.mock('@/context/i18n', () => ({ useLocale: vi.fn(() => 'en-US'), })) +// Mock the i18n context + describe('useFormatTimeFromNow', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/i18n-config/__tests__/plural-selector.spec.ts b/web/i18n-config/__tests__/plural-selector.spec.ts index 406b19130ca..36ccdd2a1e9 100644 --- a/web/i18n-config/__tests__/plural-selector.spec.ts +++ b/web/i18n-config/__tests__/plural-selector.spec.ts @@ -1,5 +1,5 @@ import type { SelectorParam } from 'i18next' -import { createInstance, keyFromSelector } from 'i18next' +import { createInstance } from 'i18next' import { describe, expect, it } from 'vitest' import { getInitOptions } from '../settings' @@ -23,12 +23,10 @@ describe('i18n selector configuration', () => { const memberKey: SelectorParam<'app'> = $ => $['accessControlDialog.members'] // Act - const selectorKey = keyFromSelector(memberKey) const singular = instance.t(memberKey, { count: 1 }) const plural = instance.t(memberKey, { count: 2 }) // Assert - expect(selectorKey).toBe('accessControlDialog.members') expect(singular).toBe('1 member') expect(plural).toBe('2 members') }) diff --git a/web/service/__tests__/use-explore.spec.tsx b/web/service/__tests__/use-explore.spec.tsx index 6db3d634235..b1341843389 100644 --- a/web/service/__tests__/use-explore.spec.tsx +++ b/web/service/__tests__/use-explore.spec.tsx @@ -6,10 +6,6 @@ import { AppModeEnum } from '@/types/app' import { fetchAppList } from '../explore' import { useExploreAppList } from '../use-explore' -vi.mock('@/context/i18n', () => ({ - useLocale: () => 'en-US', -})) - vi.mock('../explore', () => ({ fetchAppList: vi.fn(), })) diff --git a/web/test/__tests__/i18n-mock.spec.ts b/web/test/__tests__/i18n-mock.spec.ts index ba90171aa09..85ee200e1f5 100644 --- a/web/test/__tests__/i18n-mock.spec.ts +++ b/web/test/__tests__/i18n-mock.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { createReactI18nextMock, withSelectorKey, withSelectorKeyProps } from '../i18n-mock' +import { createI18nextMock, createReactI18nextMock, withSelectorKey, withSelectorKeyProps } from '../i18n-mock' describe('createReactI18nextMock', () => { describe('Selector Keys', () => { @@ -57,13 +57,25 @@ describe('createReactI18nextMock', () => { expect(result).toBe('app.accessControlDialog.title') }) + it('should preserve nested keys selected with property access', () => { + // Arrange + const { useTranslation } = createReactI18nextMock() + const { t } = useTranslation('common') + + // Act + const result = t($ => $['operation.close']) + + // Assert + expect(result).toBe('common.operation.close') + }) + it('should resolve selectors from secondary namespaces', () => { // Arrange const { useTranslation } = createReactI18nextMock() const { t } = useTranslation(['app', 'common'] as const) // Act - const result = t($ => $.common['operation.close']) + const result = t($ => $.common!['operation.close']) // Assert expect(result).toBe('common.operation.close') @@ -88,4 +100,19 @@ describe('createReactI18nextMock', () => { }) }) }) + + describe('Shared Defaults', () => { + it('should provide stable language and getI18n defaults', () => { + const i18n = createReactI18nextMock() + + expect(i18n.useTranslation().i18n.language).toBe('en-US') + expect(i18n.getI18n().t('operation.close', { ns: 'common' })).toBe('operation.close') + }) + + it('should create a standalone i18next mock without namespace formatting', () => { + const i18n = createI18nextMock() + + expect(i18n.t($ => $['operation.close'], { ns: 'common' })).toBe('operation.close') + }) + }) }) diff --git a/web/test/i18n-mock.ts b/web/test/i18n-mock.ts index 072f59199ce..9b16ae5b113 100644 --- a/web/test/i18n-mock.ts +++ b/web/test/i18n-mock.ts @@ -1,15 +1,21 @@ -import type { Namespace, SelectorParam } from 'i18next' -import { keyFromSelector } from 'i18next' import * as React from 'react' import { vi } from 'vitest' type TranslationMap = Record -type TranslationKey = string | SelectorParam -type TranslationNamespace = Namespace -type SelectorI18nKeyProps = Omit +type TranslationNamespace = string | readonly string[] +type TranslationSelectorSource = { + [key: string]: TranslationSelectorSource +} +type TranslationSelector = (source: TranslationSelectorSource) => unknown +type TranslationKey = string | TranslationSelector +type TranslationFunction = { + (key: TranslationSelector, ...args: Args): Result + (key: unknown, ...args: Args): Result +} +type SelectorI18nKeyProps = Omit & (Props extends { i18nKey: unknown } - ? { i18nKey: TranslationKey } - : { i18nKey?: TranslationKey }) + ? { i18nKey: TranslationKey } + : { i18nKey?: TranslationKey }) & (Props extends { ns: unknown } ? { ns: Ns } : { ns?: Ns }) function splitNamespacedKey(key: string) { @@ -23,17 +29,48 @@ function splitNamespacedKey(key: string) { } } -function resolveI18nKey(key: TranslationKey, namespace?: Ns) { +const selectorPath = Symbol('selectorPath') + +function createSelectorSource(path: readonly string[] = []): TranslationSelectorSource { + return new Proxy({} as TranslationSelectorSource, { + get: (_, property) => { + if (property === selectorPath) + return path + if (typeof property !== 'string') + return undefined + return createSelectorSource([...path, property]) + }, + }) +} + +function getSelectorPath(selector: TranslationSelector) { + const selected = selector(createSelectorSource()) + if ((typeof selected !== 'object' && typeof selected !== 'function') || selected === null) + throw new TypeError('Translation selectors must return a translation key') + + const path = Reflect.get(selected, selectorPath) as readonly string[] | undefined + if (!path?.length) + throw new TypeError('Translation selectors must return a translation key') + return path +} + +function resolveSelectorKey(selector: TranslationSelector, namespace?: TranslationNamespace) { + const path = getSelectorPath(selector) + const namespaces = typeof namespace === 'string' ? [namespace] : namespace + const firstSegment = path[0] + if (firstSegment && path.length > 1 && namespaces?.includes(firstSegment)) + return `${firstSegment}:${path.slice(1).join('.')}` + return path.join('.') +} + +function resolveI18nKey(key: TranslationKey, namespace?: TranslationNamespace) { if (typeof key === 'string') return key - return keyFromSelector( - key as Parameters[0], - namespace ? { ns: namespace } : undefined, - ) + return resolveSelectorKey(key, namespace) } -function resolveTranslationKey(key: TranslationKey, namespace?: Ns) { +function resolveTranslationKey(key: TranslationKey, namespace?: TranslationNamespace) { return splitNamespacedKey(resolveI18nKey(key, namespace)) } @@ -41,26 +78,27 @@ function getPrimaryNamespace(namespace?: TranslationNamespace) { return typeof namespace === 'string' ? namespace : namespace?.[0] } -export function withSelectorKey( +export function withSelectorKey( translate: (key: string, ...args: Args) => Result, namespace: Ns, -): (key: TranslationKey, ...args: Args) => Result +): TranslationFunction export function withSelectorKey( translate: (key: string, ...args: Args) => Result, namespace?: undefined, -): (key: TranslationKey, ...args: Args) => Result +): TranslationFunction export function withSelectorKey( translate: (key: string, ...args: Args) => Result, namespace?: TranslationNamespace, ) { - return (key: TranslationKey, ...args: Args) => translate(resolveI18nKey(key, namespace as Ns), ...args) + const t = (key: unknown, ...args: Args) => translate(resolveI18nKey(key as TranslationKey, namespace), ...args) + return t as TranslationFunction } export function withSelectorKeyProps< Props extends object, Result, >(render: (props: Props) => Result) { - return (props: SelectorI18nKeyProps) => { + return (props: SelectorI18nKeyProps) => { const i18nKey = props.i18nKey const resolvedProps = { ...props, @@ -74,17 +112,25 @@ export function withSelectorKeyProps< * Create a t function with optional custom translations * Checks translations[key] first, then translations[ns.key], then returns ns.key as fallback */ -function createTFunction(translations: TranslationMap, defaultNs?: Ns) { - return (translationKey: TranslationKey, options?: Record) => { +function createTFunction( + translations: TranslationMap, + defaultNs?: Ns, + config: { includeDefaultNamespace: boolean, includeOptionNamespace: boolean, includeInterpolationOptions: boolean } = { + includeDefaultNamespace: true, + includeOptionNamespace: true, + includeInterpolationOptions: true, + }, +) { + const t = (translationKey: unknown, options?: Record) => { const optionNs = options?.ns as TranslationNamespace | undefined - const namespace = (optionNs ?? defaultNs) as Ns | undefined - const { key, namespace: keyNamespace } = resolveTranslationKey(translationKey, namespace) + const namespace = ((config.includeOptionNamespace ? optionNs : undefined) ?? (config.includeDefaultNamespace ? defaultNs : undefined)) as Ns | undefined + const { key, namespace: keyNamespace } = resolveTranslationKey(translationKey as TranslationKey, namespace) // Check custom translations first (without namespace) if (translations[key] !== undefined) return translations[key] - const ns = keyNamespace ?? getPrimaryNamespace(optionNs ?? defaultNs) + const ns = keyNamespace ?? getPrimaryNamespace((config.includeOptionNamespace ? optionNs : undefined) ?? (config.includeDefaultNamespace ? defaultNs : undefined)) const fullKey = ns ? `${ns}.${key}` : key // Check custom translations with namespace @@ -94,9 +140,10 @@ function createTFunction(translations: TranslationMap, def // Serialize params (excluding ns) for test assertions const params = { ...options } delete params.ns - const suffix = Object.keys(params).length > 0 ? `:${JSON.stringify(params)}` : '' + const suffix = config.includeInterpolationOptions && Object.keys(params).length > 0 ? `:${JSON.stringify(params)}` : '' return `${fullKey}${suffix}` } + return t as TranslationFunction<[options?: Record], ReturnType> } /** @@ -111,17 +158,20 @@ function createTFunction(translations: TranslationMap, def * 'operation.confirm': 'Confirm', * })) */ -function createUseTranslationMock(translations: TranslationMap = {}) { +function createUseTranslationMock( + translations: TranslationMap = {}, +) { const tCache = new Map() const i18n = { - language: 'en', + language: 'en-US', changeLanguage: vi.fn(), } return { - useTranslation: (defaultNs?: Ns) => { + useTranslation: (defaultNs?: Ns) => { const cacheKey = typeof defaultNs === 'string' ? defaultNs : defaultNs?.join(',') ?? '' - if (!tCache.has(cacheKey)) + if (!tCache.has(cacheKey)) { tCache.set(cacheKey, createTFunction(translations, defaultNs)) + } return { t: tCache.get(cacheKey)! as ReturnType>, i18n, @@ -135,8 +185,8 @@ function createUseTranslationMock(translations: TranslationMap = {}) { */ function createTransMock(translations: TranslationMap = {}) { return { - Trans: ({ i18nKey, ns, children }: { - i18nKey: TranslationKey + Trans: ({ i18nKey, ns, children }: { + i18nKey: TranslationKey ns?: Ns children?: React.ReactNode }) => { @@ -158,9 +208,40 @@ function createTransMock(translations: TranslationMap = {}) { * 'modal.title': 'My Modal', * })) */ -export function createReactI18nextMock(translations: TranslationMap = {}) { +export function createReactI18nextMock( + translations: TranslationMap = {}, +) { + const useTranslationMock = createUseTranslationMock(translations) + const i18nextMock = createI18nextMock(translations) return { - ...createUseTranslationMock(translations), + ...useTranslationMock, ...createTransMock(translations), + getI18n: () => ({ + ...i18nextMock, + language: 'en-US', + }), + } +} + +export function createI18nextMock( + translations: TranslationMap = {}, +) { + return { + t: createTFunction(translations, undefined, { + includeDefaultNamespace: false, + includeOptionNamespace: false, + includeInterpolationOptions: false, + }), + } +} + +export function createReactI18nextLanguageMock(language: string) { + const mock = createReactI18nextMock() + return { + ...mock, + getI18n: () => ({ + ...mock.getI18n(), + language, + }), } }