test(web): centralize i18n mocks (#38670)

This commit is contained in:
Stephen Zhou 2026-07-10 22:08:33 +08:00 committed by GitHub
parent 6f47950fe2
commit e3a08b40f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
138 changed files with 519 additions and 1332 deletions

View File

@ -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,

View File

@ -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<string, unknown>) => unknown) => selector({
appDetail: mockAppDetail,

View File

@ -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<string, unknown>) => 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(<AppPublisher publishedAt={1700000000} />)
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(<AppPublisher publishedAt={1700000000} />)
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(?=$|:)/))
})
})
})

View File

@ -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),

View File

@ -73,10 +73,6 @@ vi.mock('@/context/modal-context', () => ({
}),
}))
vi.mock('@/context/i18n', () => ({
useGetLanguage: () => 'en-US',
}))
// ─── Service mocks ───────────────────────────────────────────────────────────
vi.mock('@/service/use-education', () => ({
useEducationVerify: () => ({

View File

@ -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' }),

View File

@ -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(),
}))

View File

@ -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',
)

View File

@ -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 }),
}))

View File

@ -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<typeof useMembers>)
}
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,

View File

@ -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(),

View File

@ -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<typeof import('@/service/use-common')>(),
useLogout: () => ({

View File

@ -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 }),
}))

View File

@ -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<typeof import('@/utils')>()
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, () => ({

View File

@ -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<string, unknown>[] }))

View File

@ -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(),
}))

View File

@ -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,

View File

@ -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 }) => (
<div data-testid="filter">{children}</div>

View File

@ -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(<CSVDownload />)

View File

@ -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<string, any>) => {
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()

View File

@ -37,16 +37,6 @@ const hotkeyMocks = vi.hoisted(() => ({
let mockAppDetail: Record<string, any> | 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'))

View File

@ -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'))

View File

@ -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<string, unknown>) => void }) => (
<button type="button" onClick={() => onSelect({ model: 'gpt-4o' })}>publish-multiple-model</button>
@ -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(<AccessModeDisplay />).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(
<PublisherActionsSection
@ -314,6 +305,6 @@ describe('app-publisher sections', () => {
/>,
)
expect(screen.queryByText('common.runApp')).not.toBeInTheDocument()
expect(screen.queryByText(/(?:^|\.)common\.runApp(?=$|:)/)).not.toBeInTheDocument()
})
})

View File

@ -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',

View File

@ -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',

View File

@ -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}}', [])
})
})

View File

@ -12,16 +12,6 @@ import ConfigModal from '../index'
const toastErrorSpy = vi.spyOn(toast, 'error').mockReturnValue('toast-error')
let latestFormProps: Record<string, any> | 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<string, any>) => {
latestFormProps = props

View File

@ -55,10 +55,6 @@ vi.mock('@/app/components/plugins/readme-panel/entrance', () => ({
ReadmeEntrance: ({ className }: { className?: string }) => <div className={className}>readme</div>,
}))
vi.mock('@/context/i18n', () => ({
useLocale: vi.fn(() => 'en-US'),
}))
const createParameter = (overrides?: Partial<ToolParameter>): ToolParameter => ({
name: 'settingParam',
label: {

View File

@ -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(<AutomaticBtn onClick={mockOnClick} />)
expect(screen.getByText('operation.automatic')).toBeInTheDocument()
expect(screen.getByText(/(?:^|\.)operation\.automatic(?=$|:)/)).toBeInTheDocument()
})
it('should render the sparkling icon', () => {

View File

@ -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({

View File

@ -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(<IdeaOutput value="Initial idea" onChange={onChange} />)
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()
})
})

View File

@ -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',

View File

@ -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,
}),
}),

View File

@ -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 },

View File

@ -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()
})
})
})

View File

@ -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')

View File

@ -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(<ChatUserInput inputs={{}} />)
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(<ChatUserInput inputs={{}} />)
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', () => {

View File

@ -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<any>('ahooks')

View File

@ -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()
})
})

View File

@ -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)

View File

@ -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(?=$|:)/))
})
})

View File

@ -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()

View File

@ -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',

View File

@ -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(<Filter {...defaultProps} />)
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(<Filter {...defaultProps} isChatMode />)
expect(screen.getByPlaceholderText('operation.search'))!.toBeInTheDocument()
expect(screen.getByPlaceholderText(/(?:^|\.)operation\.search(?=$|:)/))!.toBeInTheDocument()
})
it('should not render sort component when not in chat mode', () => {
render(<Filter {...defaultProps} isChatMode={false} />)
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(<Filter {...defaultProps} />)
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(<Filter {...propsWithKeyword} />)
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(<Filter {...propsWithPeriod} />)
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(<Filter {...propsWithNotAnnotated} />)
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(<Filter {...propsWithSort} />)
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(<Filter {...propsWithDescSort} />)
expect(screen.getByPlaceholderText('operation.search'))!.toBeInTheDocument()
expect(screen.getByPlaceholderText(/(?:^|\.)operation\.search(?=$|:)/))!.toBeInTheDocument()
})
})
})

View File

@ -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: <T,>(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()
})

View File

@ -24,16 +24,6 @@ let mockShowPromptLogModal = false
let mockShowAgentLogModal = false
let mockCurrentLogItem: Record<string, unknown> | 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'))

View File

@ -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(<ModelInfo model={defaultModel} />)
fireEvent.click(screen.getByRole('button'))
expect(screen.getByText('detail.modelParams')).toBeInTheDocument()
expect(screen.getByText(/(?:^|\.)detail\.modelParams(?=$|:)/)).toBeInTheDocument()
})
it('should render temperature parameter', () => {

View File

@ -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 }) => (
<div data-testid="image-preview" data-url={url} data-title={title}>
@ -31,7 +22,7 @@ describe('VarPanel', () => {
it('should render variables section header', () => {
render(<VarPanel {...defaultProps} />)
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(<VarPanel {...defaultProps} />)
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(<VarPanel {...defaultProps} />)
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(<VarPanel {...defaultProps} />)
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(<VarPanel {...defaultProps} />)
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(<VarPanel {...propsWithFiles} />)
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(<VarPanel {...emptyProps} />)
expect(screen.getByText('detail.variables')).toBeInTheDocument()
expect(screen.getByText(/(?:^|\.)detail\.variables(?=$|:)/)).toBeInTheDocument()
})
})

View File

@ -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 }) => <span>{i18nKey}</span>),
})
})
vi.mock('../settings', () => ({
default: () => <div data-testid="settings-modal" />,
}))
@ -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()
})

View File

@ -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)

View File

@ -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()

View File

@ -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(<TriggerCard appInfo={mockAppInfo} onToggleResult={mockOnToggleResult} />)
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(<TriggerCard appInfo={mockAppInfo} onToggleResult={mockOnToggleResult} />)
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(<TriggerCard appInfo={mockAppInfo} onToggleResult={mockOnToggleResult} />)
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(<TriggerCard appInfo={mockAppInfo} onToggleResult={mockOnToggleResult} />)
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(<TriggerCard appInfo={mockAppInfo} onToggleResult={mockOnToggleResult} />)
expect(screen.getByText('overview.status.running')).toBeInTheDocument()
expect(screen.getByText(/(?:^|\.)overview\.status\.running(?=$|:)/)).toBeInTheDocument()
})
it('should show disable status for disabled triggers', () => {
render(<TriggerCard appInfo={mockAppInfo} onToggleResult={mockOnToggleResult} />)
expect(screen.getByText('overview.status.disable')).toBeInTheDocument()
expect(screen.getByText(/(?:^|\.)overview\.status\.disable(?=$|:)/)).toBeInTheDocument()
})
it('should render block icons for each trigger', () => {

View File

@ -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 })
})
})

View File

@ -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()
})
})

View File

@ -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: () => <div>workflow-process</div>,
}))
@ -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')
})

View File

@ -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,
},
],

View File

@ -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<string, string> = {
'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(<ReasoningPanel content={{ llm: 'let me think' }} done={false} />)
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(<ReasoningPanel content={{ llm: 'done thinking' }} done />)
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', () => {

View File

@ -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 } }),
}))

View File

@ -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<string, string> = {
'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()
})
})
})

View File

@ -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()
})

View File

@ -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'],
}))

View File

@ -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'],
}))

View File

@ -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>): DefaultModel => ({

View File

@ -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', () => ({

View File

@ -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'],

View File

@ -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 }: {

View File

@ -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[] = [
{

View File

@ -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 }) => (

View File

@ -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()

View File

@ -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()

View File

@ -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,
}))

View File

@ -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,

View File

@ -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 }) => (
<div data-testid="divider" className={className} />
@ -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()
})
})

View File

@ -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,

View File

@ -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<string, unknown>) => {
if (key === 'banner.greeting')
return `Welcome back, ${opts?.name} 👋`

View File

@ -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 }),
}))

View File

@ -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,

View File

@ -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()

View File

@ -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 },

View File

@ -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', () => ({

View File

@ -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,
}))

View File

@ -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()

View File

@ -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<Record<string, unknown>>)

View File

@ -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: {

View File

@ -73,11 +73,6 @@ vi.mock('@langgenius/dify-ui/tooltip', () => ({
TooltipTrigger: ({ render }: { render: ReactNode }) => <>{render}</>,
TooltipContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}))
vi.mock('@/context/i18n', () => ({
useLocale: () => 'en-US',
}))
vi.mock('@/hooks/use-theme', () => ({
default: () => ({ theme: 'light' }),
}))

View File

@ -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 }) => (
<div>

View File

@ -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: [],

View File

@ -90,11 +90,6 @@ vi.mock('@/app/components/plugins/update-plugin/plugin-version-picker', () => ({
</div>
),
}))
vi.mock('@/context/i18n', () => ({
useLocale: () => 'en-US',
}))
vi.mock('@/hooks/use-theme', () => ({
default: () => ({ theme: 'light' }),
}))

View File

@ -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(<IntegrationsToolProviderCard collection={createCollection()} />)
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', () => {

View File

@ -21,16 +21,6 @@ vi.mock('@/app/components/header', () => ({
vi.mock('@/app/components/header/header-wrapper', () => ({
default: ({ children }: { children: ReactNode }) => <div data-testid="header-wrapper">{children}</div>,
}))
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<typeof import('@tanstack/react-query')>()
return {
@ -130,7 +120,7 @@ describe('MainNavLayout', () => {
it('renders skip navigation before the repeated main navigation', () => {
const { container } = render(<MainNavLayout><div>content</div></MainNavLayout>)
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(<MainNavLayout><div>content</div></MainNavLayout>)
const skipLink = screen.getByRole('link', { name: 'navigation.skipToMain' })
const skipLink = screen.getByRole('link', { name: /(?:^|\.)navigation\.skipToMain(?=$|:)/ })
const main = screen.getByRole('main')
fireEvent.click(skipLink)

View File

@ -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<string, string>) => value['en-US'] || value.en_US,
}))

View File

@ -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: () => <div data-testid="plugin-type-switch">PluginTypeSwitch</div>,

View File

@ -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 }) => (

View File

@ -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}`,

View File

@ -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' },

View File

@ -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,
}))

View File

@ -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],

View File

@ -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<string, Tag> = {
agent: { name: 'agent', label: 'Agent' },
rag: { name: 'rag', label: 'RAG' },

View File

@ -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', () => ({

View File

@ -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(' '),
}))

Some files were not shown because too many files have changed in this diff Show More