mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 20:17:29 +08:00
refactor: create shared react-i18next mock to reduce duplication (#29711)
This commit is contained in:
parent
4cc6652424
commit
eeb5129a17
@ -76,7 +76,7 @@ Use this checklist when generating or reviewing tests for Dify frontend componen
|
|||||||
- [ ] **DO NOT mock base components** (`@/app/components/base/*`)
|
- [ ] **DO NOT mock base components** (`@/app/components/base/*`)
|
||||||
- [ ] `jest.clearAllMocks()` in `beforeEach` (not `afterEach`)
|
- [ ] `jest.clearAllMocks()` in `beforeEach` (not `afterEach`)
|
||||||
- [ ] Shared mock state reset in `beforeEach`
|
- [ ] Shared mock state reset in `beforeEach`
|
||||||
- [ ] i18n mock returns keys (not empty strings)
|
- [ ] i18n uses shared mock (auto-loaded); only override locally for custom translations
|
||||||
- [ ] Router mocks match actual Next.js API
|
- [ ] Router mocks match actual Next.js API
|
||||||
- [ ] Mocks reflect actual component conditional behavior
|
- [ ] Mocks reflect actual component conditional behavior
|
||||||
- [ ] Only mock: API services, complex context providers, third-party libs
|
- [ ] Only mock: API services, complex context providers, third-party libs
|
||||||
|
|||||||
@ -318,3 +318,4 @@ For more detailed information, refer to:
|
|||||||
- `web/jest.config.ts` - Jest configuration
|
- `web/jest.config.ts` - Jest configuration
|
||||||
- `web/jest.setup.ts` - Test environment setup
|
- `web/jest.setup.ts` - Test environment setup
|
||||||
- `web/testing/analyze-component.js` - Component analysis tool
|
- `web/testing/analyze-component.js` - Component analysis tool
|
||||||
|
- `web/__mocks__/react-i18next.ts` - Shared i18n mock (auto-loaded by Jest, no explicit mock needed; override locally only for custom translations)
|
||||||
|
|||||||
@ -46,12 +46,22 @@ Only mock these categories:
|
|||||||
|
|
||||||
## Essential Mocks
|
## Essential Mocks
|
||||||
|
|
||||||
### 1. i18n (Always Required)
|
### 1. i18n (Auto-loaded via Shared Mock)
|
||||||
|
|
||||||
|
A shared mock is available at `web/__mocks__/react-i18next.ts` and is auto-loaded by Jest.
|
||||||
|
**No explicit mock needed** for most tests - it returns translation keys as-is.
|
||||||
|
|
||||||
|
For tests requiring custom translations, override the mock:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
jest.mock('react-i18next', () => ({
|
jest.mock('react-i18next', () => ({
|
||||||
useTranslation: () => ({
|
useTranslation: () => ({
|
||||||
t: (key: string) => key,
|
t: (key: string) => {
|
||||||
|
const translations: Record<string, string> = {
|
||||||
|
'my.custom.key': 'Custom translation',
|
||||||
|
}
|
||||||
|
return translations[key] || key
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
```
|
```
|
||||||
@ -313,7 +323,7 @@ Need to use a component in test?
|
|||||||
│ └─ YES → Mock it (next/navigation, external SDKs)
|
│ └─ YES → Mock it (next/navigation, external SDKs)
|
||||||
│
|
│
|
||||||
└─ Is it i18n?
|
└─ Is it i18n?
|
||||||
└─ YES → Mock to return keys
|
└─ YES → Uses shared mock (auto-loaded). Override only for custom translations
|
||||||
```
|
```
|
||||||
|
|
||||||
## Factory Function Pattern
|
## Factory Function Pattern
|
||||||
|
|||||||
34
web/__mocks__/react-i18next.ts
Normal file
34
web/__mocks__/react-i18next.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Shared mock for react-i18next
|
||||||
|
*
|
||||||
|
* Jest automatically uses this mock when react-i18next is imported in tests.
|
||||||
|
* The default behavior returns the translation key as-is, which is suitable
|
||||||
|
* for most test scenarios.
|
||||||
|
*
|
||||||
|
* For tests that need custom translations, you can override with jest.mock():
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* jest.mock('react-i18next', () => ({
|
||||||
|
* useTranslation: () => ({
|
||||||
|
* t: (key: string) => {
|
||||||
|
* if (key === 'some.key') return 'Custom translation'
|
||||||
|
* return key
|
||||||
|
* },
|
||||||
|
* }),
|
||||||
|
* }))
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const useTranslation = () => ({
|
||||||
|
t: (key: string) => key,
|
||||||
|
i18n: {
|
||||||
|
language: 'en',
|
||||||
|
changeLanguage: jest.fn(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Trans = ({ children }: { children?: React.ReactNode }) => children
|
||||||
|
|
||||||
|
export const initReactI18next = {
|
||||||
|
type: '3rdParty',
|
||||||
|
init: jest.fn(),
|
||||||
|
}
|
||||||
@ -4,12 +4,6 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|||||||
import MailAndPasswordAuth from '@/app/(shareLayout)/webapp-signin/components/mail-and-password-auth'
|
import MailAndPasswordAuth from '@/app/(shareLayout)/webapp-signin/components/mail-and-password-auth'
|
||||||
import CheckCode from '@/app/(shareLayout)/webapp-signin/check-code/page'
|
import CheckCode from '@/app/(shareLayout)/webapp-signin/check-code/page'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
const replaceMock = jest.fn()
|
const replaceMock = jest.fn()
|
||||||
const backMock = jest.fn()
|
const backMock = jest.fn()
|
||||||
|
|
||||||
|
|||||||
@ -4,12 +4,6 @@ import '@testing-library/jest-dom'
|
|||||||
import CommandSelector from '../../app/components/goto-anything/command-selector'
|
import CommandSelector from '../../app/components/goto-anything/command-selector'
|
||||||
import type { ActionItem } from '../../app/components/goto-anything/actions/types'
|
import type { ActionItem } from '../../app/components/goto-anything/actions/types'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('cmdk', () => ({
|
jest.mock('cmdk', () => ({
|
||||||
Command: {
|
Command: {
|
||||||
Group: ({ children, className }: any) => <div className={className}>{children}</div>,
|
Group: ({ children, className }: any) => <div className={className}>{children}</div>,
|
||||||
|
|||||||
@ -3,13 +3,6 @@ import { render } from '@testing-library/react'
|
|||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import { OpikIconBig } from '@/app/components/base/icons/src/public/tracing'
|
import { OpikIconBig } from '@/app/components/base/icons/src/public/tracing'
|
||||||
|
|
||||||
// Mock dependencies to isolate the SVG rendering issue
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('SVG Attribute Error Reproduction', () => {
|
describe('SVG Attribute Error Reproduction', () => {
|
||||||
// Capture console errors
|
// Capture console errors
|
||||||
const originalError = console.error
|
const originalError = console.error
|
||||||
|
|||||||
@ -3,12 +3,6 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|||||||
import CSVUploader, { type Props } from './csv-uploader'
|
import CSVUploader, { type Props } from './csv-uploader'
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
import { ToastContext } from '@/app/components/base/toast'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('CSVUploader', () => {
|
describe('CSVUploader', () => {
|
||||||
const notify = jest.fn()
|
const notify = jest.fn()
|
||||||
const updateFile = jest.fn()
|
const updateFile = jest.fn()
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import OperationBtn from './index'
|
import OperationBtn from './index'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('@remixicon/react', () => ({
|
jest.mock('@remixicon/react', () => ({
|
||||||
RiAddLine: (props: { className?: string }) => (
|
RiAddLine: (props: { className?: string }) => (
|
||||||
<svg data-testid='add-icon' className={props.className} />
|
<svg data-testid='add-icon' className={props.className} />
|
||||||
|
|||||||
@ -2,12 +2,6 @@ import React from 'react'
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import ConfirmAddVar from './index'
|
import ConfirmAddVar from './index'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../../base/var-highlight', () => ({
|
jest.mock('../../base/var-highlight', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: ({ name }: { name: string }) => <span data-testid="var-highlight">{name}</span>,
|
default: ({ name }: { name: string }) => <span data-testid="var-highlight">{name}</span>,
|
||||||
|
|||||||
@ -3,12 +3,6 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
|||||||
import EditModal from './edit-modal'
|
import EditModal from './edit-modal'
|
||||||
import type { ConversationHistoriesRole } from '@/models/debug'
|
import type { ConversationHistoriesRole } from '@/models/debug'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('@/app/components/base/modal', () => ({
|
jest.mock('@/app/components/base/modal', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||||
|
|||||||
@ -2,12 +2,6 @@ import React from 'react'
|
|||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import HistoryPanel from './history-panel'
|
import HistoryPanel from './history-panel'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
const mockDocLink = jest.fn(() => 'doc-link')
|
const mockDocLink = jest.fn(() => 'doc-link')
|
||||||
jest.mock('@/context/i18n', () => ({
|
jest.mock('@/context/i18n', () => ({
|
||||||
useDocLink: () => mockDocLink,
|
useDocLink: () => mockDocLink,
|
||||||
|
|||||||
@ -6,12 +6,6 @@ import { MAX_PROMPT_MESSAGE_LENGTH } from '@/config'
|
|||||||
import { type PromptItem, PromptRole, type PromptVariable } from '@/models/debug'
|
import { type PromptItem, PromptRole, type PromptVariable } from '@/models/debug'
|
||||||
import { AppModeEnum, ModelModeType } from '@/types/app'
|
import { AppModeEnum, ModelModeType } from '@/types/app'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
type DebugConfiguration = {
|
type DebugConfiguration = {
|
||||||
isAdvancedMode: boolean
|
isAdvancedMode: boolean
|
||||||
currentAdvancedPrompt: PromptItem | PromptItem[]
|
currentAdvancedPrompt: PromptItem | PromptItem[]
|
||||||
|
|||||||
@ -5,12 +5,6 @@ jest.mock('react-sortablejs', () => ({
|
|||||||
ReactSortable: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
ReactSortable: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('ConfigSelect Component', () => {
|
describe('ConfigSelect Component', () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
options: ['Option 1', 'Option 2'],
|
options: ['Option 1', 'Option 2'],
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import ContrlBtnGroup from './index'
|
import ContrlBtnGroup from './index'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('ContrlBtnGroup', () => {
|
describe('ContrlBtnGroup', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|||||||
@ -51,12 +51,6 @@ const mockFiles: FileEntity[] = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('@/context/debug-configuration', () => ({
|
jest.mock('@/context/debug-configuration', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
useDebugConfigurationContext: () => mockUseDebugConfigurationContext(),
|
useDebugConfigurationContext: () => mockUseDebugConfigurationContext(),
|
||||||
|
|||||||
@ -18,12 +18,6 @@ import type { App, AppIconType, AppModeEnum } from '@/types/app'
|
|||||||
// Mocks
|
// Mocks
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
const mockRouterPush = jest.fn()
|
const mockRouterPush = jest.fn()
|
||||||
jest.mock('next/navigation', () => ({
|
jest.mock('next/navigation', () => ({
|
||||||
useRouter: () => ({
|
useRouter: () => ({
|
||||||
|
|||||||
@ -16,12 +16,6 @@ import type { QueryParam } from './index'
|
|||||||
// Mocks
|
// Mocks
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
const mockTrackEvent = jest.fn()
|
const mockTrackEvent = jest.fn()
|
||||||
jest.mock('@/app/components/base/amplitude/utils', () => ({
|
jest.mock('@/app/components/base/amplitude/utils', () => ({
|
||||||
trackEvent: (...args: unknown[]) => mockTrackEvent(...args),
|
trackEvent: (...args: unknown[]) => mockTrackEvent(...args),
|
||||||
|
|||||||
@ -49,13 +49,6 @@ jest.mock('next/navigation', () => ({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
Trans: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('next/link', () => ({
|
jest.mock('next/link', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: ({ children, href }: { children: React.ReactNode; href: string }) => <a href={href}>{children}</a>,
|
default: ({ children, href }: { children: React.ReactNode; href: string }) => <a href={href}>{children}</a>,
|
||||||
|
|||||||
@ -22,12 +22,6 @@ import { APP_PAGE_LIMIT } from '@/config'
|
|||||||
// Mocks
|
// Mocks
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
const mockRouterPush = jest.fn()
|
const mockRouterPush = jest.fn()
|
||||||
jest.mock('next/navigation', () => ({
|
jest.mock('next/navigation', () => ({
|
||||||
useRouter: () => ({
|
useRouter: () => ({
|
||||||
|
|||||||
@ -15,12 +15,6 @@ import { Theme } from '@/types/app'
|
|||||||
// Mocks
|
// Mocks
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
let mockTheme = Theme.light
|
let mockTheme = Theme.light
|
||||||
jest.mock('@/hooks/use-theme', () => ({
|
jest.mock('@/hooks/use-theme', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
|
|||||||
@ -3,13 +3,6 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|||||||
import { AppModeEnum } from '@/types/app'
|
import { AppModeEnum } from '@/types/app'
|
||||||
import { AccessMode } from '@/models/access-control'
|
import { AccessMode } from '@/models/access-control'
|
||||||
|
|
||||||
// Mock react-i18next - return key as per testing skills
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Mock next/navigation
|
// Mock next/navigation
|
||||||
const mockPush = jest.fn()
|
const mockPush = jest.fn()
|
||||||
jest.mock('next/navigation', () => ({
|
jest.mock('next/navigation', () => ({
|
||||||
|
|||||||
@ -2,13 +2,6 @@ import React from 'react'
|
|||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import Empty from './empty'
|
import Empty from './empty'
|
||||||
|
|
||||||
// Mock react-i18next - return key as per testing skills
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('Empty', () => {
|
describe('Empty', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|||||||
@ -2,13 +2,6 @@ import React from 'react'
|
|||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import Footer from './footer'
|
import Footer from './footer'
|
||||||
|
|
||||||
// Mock react-i18next - return key as per testing skills
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('Footer', () => {
|
describe('Footer', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|||||||
@ -1,13 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
|
|
||||||
// Mock react-i18next - return key as per testing skills
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Track mock calls
|
// Track mock calls
|
||||||
let documentTitleCalls: string[] = []
|
let documentTitleCalls: string[] = []
|
||||||
let educationInitCalls: number = 0
|
let educationInitCalls: number = 0
|
||||||
|
|||||||
@ -2,13 +2,6 @@ import React from 'react'
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import { AppModeEnum } from '@/types/app'
|
import { AppModeEnum } from '@/types/app'
|
||||||
|
|
||||||
// Mock react-i18next - return key as per testing skills
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Mock next/navigation
|
// Mock next/navigation
|
||||||
const mockReplace = jest.fn()
|
const mockReplace = jest.fn()
|
||||||
const mockRouter = { replace: mockReplace }
|
const mockRouter = { replace: mockReplace }
|
||||||
|
|||||||
@ -1,13 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
|
||||||
// Mock react-i18next - return key as per testing skills
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Mock next/navigation
|
// Mock next/navigation
|
||||||
const mockReplace = jest.fn()
|
const mockReplace = jest.fn()
|
||||||
jest.mock('next/navigation', () => ({
|
jest.mock('next/navigation', () => ({
|
||||||
|
|||||||
@ -6,13 +6,6 @@ import type { IDrawerProps } from './index'
|
|||||||
// Capture dialog onClose for testing
|
// Capture dialog onClose for testing
|
||||||
let capturedDialogOnClose: (() => void) | null = null
|
let capturedDialogOnClose: (() => void) | null = null
|
||||||
|
|
||||||
// Mock react-i18next
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Mock @headlessui/react
|
// Mock @headlessui/react
|
||||||
jest.mock('@headlessui/react', () => ({
|
jest.mock('@headlessui/react', () => ({
|
||||||
Dialog: ({ children, open, onClose, className, unmount }: {
|
Dialog: ({ children, open, onClose, className, unmount }: {
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import Label from './label'
|
import Label from './label'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('Label Component', () => {
|
describe('Label Component', () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
htmlFor: 'test-input',
|
htmlFor: 'test-input',
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import { InputNumber } from './index'
|
import { InputNumber } from './index'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('InputNumber Component', () => {
|
describe('InputNumber Component', () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
onChange: jest.fn(),
|
onChange: jest.fn(),
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import AnnotationFull from './index'
|
import AnnotationFull from './index'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
let mockUsageProps: { className?: string } | null = null
|
let mockUsageProps: { className?: string } | null = null
|
||||||
jest.mock('./usage', () => ({
|
jest.mock('./usage', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import AnnotationFullModal from './modal'
|
import AnnotationFullModal from './modal'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
let mockUsageProps: { className?: string } | null = null
|
let mockUsageProps: { className?: string } | null = null
|
||||||
jest.mock('./usage', () => ({
|
jest.mock('./usage', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
|
|||||||
@ -5,12 +5,6 @@ import PlanUpgradeModal from './index'
|
|||||||
|
|
||||||
const mockSetShowPricingModal = jest.fn()
|
const mockSetShowPricingModal = jest.fn()
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('@/app/components/base/modal', () => {
|
jest.mock('@/app/components/base/modal', () => {
|
||||||
const MockModal = ({ isShow, children }: { isShow: boolean; children: React.ReactNode }) => (
|
const MockModal = ({ isShow, children }: { isShow: boolean; children: React.ReactNode }) => (
|
||||||
isShow ? <div data-testid="plan-upgrade-modal">{children}</div> : null
|
isShow ? <div data-testid="plan-upgrade-modal">{children}</div> : null
|
||||||
|
|||||||
@ -16,13 +16,6 @@ jest.mock('next/navigation', () => ({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Mock react-i18next
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Mock useDocLink hook
|
// Mock useDocLink hook
|
||||||
jest.mock('@/context/i18n', () => ({
|
jest.mock('@/context/i18n', () => ({
|
||||||
useDocLink: () => (path?: string) => `https://docs.dify.ai/en${path || ''}`,
|
useDocLink: () => (path?: string) => `https://docs.dify.ai/en${path || ''}`,
|
||||||
|
|||||||
@ -15,13 +15,6 @@ jest.mock('next/navigation', () => ({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Mock react-i18next
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Mock useDocLink hook
|
// Mock useDocLink hook
|
||||||
jest.mock('@/context/i18n', () => ({
|
jest.mock('@/context/i18n', () => ({
|
||||||
useDocLink: () => (path?: string) => `https://docs.dify.ai/en${path || ''}`,
|
useDocLink: () => (path?: string) => `https://docs.dify.ai/en${path || ''}`,
|
||||||
|
|||||||
@ -4,12 +4,6 @@ import AppCard, { type AppCardProps } from './index'
|
|||||||
import type { App } from '@/models/explore'
|
import type { App } from '@/models/explore'
|
||||||
import { AppModeEnum } from '@/types/app'
|
import { AppModeEnum } from '@/types/app'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('@/app/components/base/app-icon', () => ({
|
jest.mock('@/app/components/base/app-icon', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
default: ({ children }: any) => <div data-testid="app-icon">{children}</div>,
|
default: ({ children }: any) => <div data-testid="app-icon">{children}</div>,
|
||||||
|
|||||||
@ -2,12 +2,6 @@ import React from 'react'
|
|||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import NoData from './index'
|
import NoData from './index'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('NoData', () => {
|
describe('NoData', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|||||||
@ -2,12 +2,6 @@ import React from 'react'
|
|||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import ResDownload from './index'
|
import ResDownload from './index'
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
const mockType = { Link: 'mock-link' }
|
const mockType = { Link: 'mock-link' }
|
||||||
let capturedProps: Record<string, unknown> | undefined
|
let capturedProps: Record<string, unknown> | undefined
|
||||||
|
|
||||||
|
|||||||
@ -3,13 +3,6 @@ import { act, render, screen, waitFor } from '@testing-library/react'
|
|||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import ConfirmModal from './index'
|
import ConfirmModal from './index'
|
||||||
|
|
||||||
// Mock external dependencies as per guidelines
|
|
||||||
jest.mock('react-i18next', () => ({
|
|
||||||
useTranslation: () => ({
|
|
||||||
t: (key: string) => key,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Test utilities
|
// Test utilities
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
show: true,
|
show: true,
|
||||||
|
|||||||
@ -326,12 +326,19 @@ describe('ComponentName', () => {
|
|||||||
|
|
||||||
### General
|
### General
|
||||||
|
|
||||||
1. **i18n**: Always return key
|
1. **i18n**: Uses shared mock at `web/__mocks__/react-i18next.ts` (auto-loaded by Jest)
|
||||||
|
|
||||||
|
The shared mock returns translation keys as-is. For custom translations, override:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
jest.mock('react-i18next', () => ({
|
jest.mock('react-i18next', () => ({
|
||||||
useTranslation: () => ({
|
useTranslation: () => ({
|
||||||
t: (key: string) => key,
|
t: (key: string) => {
|
||||||
|
const translations: Record<string, string> = {
|
||||||
|
'my.custom.key': 'Custom translation',
|
||||||
|
}
|
||||||
|
return translations[key] || key
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
```
|
```
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user