test: unify i18next mocks into centralized helpers#38
Conversation
Consolidate scattered i18next mock implementations across test files into a single source of truth. This reduces duplication and ensures consistent mock behavior. - Create test/i18n-mock.ts with reusable factory functions - Update vitest.setup.ts to use the centralized helpers - Remove redundant mock definitions from 8 test files - Update testing.md documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…eateReactI18nextMock` and detail global mock provisions.
Greptile OverviewGreptile SummaryOverviewThis PR centralizes i18next mocks into reusable helper functions in What Changed:
Architecture:
Critical Issues Found🔴 Issue 1: Missing Global Mocks (Test Failures)Impact: Tests will fail for components using The documentation claims the global mock provides
Both components use these hooks, so tests will crash when trying to import the actual implementations. Required Fix: Add global mocks for 🟡 Issue 2: Missing
|
| Filename | Score | Overview |
|---|---|---|
| web/test/i18n-mock.ts | 3/5 | New centralized i18n mock helpers - missing returnObjects support and not fully integrated with global setup |
| web/vitest.setup.ts | 2/5 | Updated to use centralized mock but missing mocks for useMixedTranslation and useGetLanguage that documentation claims exist |
| web/testing/testing.md | 3/5 | Documentation updated with correct examples but incorrectly claims global mock provides useMixedTranslation and useGetLanguage |
| .claude/skills/frontend-testing/references/mocking.md | 3/5 | Documentation updated with correct examples but incorrectly claims global mock provides useMixedTranslation and useGetLanguage |
| web/app/components/plugins/card/index.spec.tsx | 1/5 | Removed local mocks for useMixedTranslation and useGetLanguage - tests will fail as these are not mocked globally |
| web/app/components/plugins/plugin-mutation-model/index.spec.tsx | 1/5 | Removed local mocks for useMixedTranslation and useGetLanguage - tests will fail as these are not mocked globally |
Sequence Diagram
sequenceDiagram
participant Test as Test File
participant Vitest as vitest.setup.ts
participant Mock as test/i18n-mock.ts
participant Component as Component Under Test
Note over Test,Component: Test Initialization
Test->>Vitest: Load global setup
Vitest->>Mock: Import createReactI18nextMock()
Mock-->>Vitest: Return { useTranslation, Trans }
Vitest->>Vitest: vi.mock('react-i18next')
Note over Vitest: ❌ Missing: useMixedTranslation, useGetLanguage
Note over Test,Component: Test with Custom Translations
Test->>Mock: Import createReactI18nextMock({ key: 'Custom' })
Test->>Test: vi.mock('react-i18next', () => ...)
Test->>Component: render(<Component />)
Component->>Mock: useTranslation()
Mock-->>Component: { t: (key) => translations[key] || key }
Component->>Mock: useMixedTranslation() ❌
Note over Component,Mock: ERROR: Not mocked!
Component->>Mock: useGetLanguage() ❌
Note over Component,Mock: ERROR: Not mocked!
Note over Test,Component: Expected Flow (After Fix)
Vitest->>Mock: Import createMixedTranslationMock()
Vitest->>Mock: Import createUseGetLanguageMock()
Vitest->>Vitest: vi.mock('@/app/components/plugins/marketplace/hooks')
Vitest->>Vitest: vi.mock('@/context/i18n')
Test->>Component: render(<Component />)
Component->>Mock: useMixedTranslation()
Mock-->>Component: ✅ { t: mockFunction }
Component->>Mock: useGetLanguage()
Mock-->>Component: ✅ 'en-US'
| export function createTFunction(translations: TranslationMap, defaultNs?: string) { | ||
| return (key: string, options?: Record<string, unknown>) => { | ||
| // Check if translations[key] is not undefined | ||
| if (translations[key] !== undefined) | ||
| return translations[key] | ||
|
|
||
| // Get namespace from options or use defaultNs | ||
| const ns = (options?.ns as string | undefined) ?? defaultNs | ||
| // Create fullKey by concatenating namespace and key with a dot | ||
| const fullKey = ns ? `${ns}.${key}` : key | ||
|
|
||
| // Check if translations[fullKey] is not undefined | ||
| if (translations[fullKey] !== undefined) | ||
| return translations[fullKey] | ||
|
|
||
| // Create a copy of options object | ||
| const params = { ...options } | ||
| // Delete the ns property from params | ||
| delete params.ns | ||
| // Delete the returnObjects property from params | ||
| delete params.returnObjects | ||
| // Create suffix by stringifying params if there are any keys | ||
| const suffix = Object.keys(params).length > 0 ? `:${JSON.stringify(params)}` : '' | ||
| // Return the fullKey concatenated with suffix | ||
| return `${fullKey}${suffix}` | ||
| } |
There was a problem hiding this comment.
The createTFunction deletes the returnObjects property but doesn't handle it. The old global mock in vitest.setup.ts had this logic:
if (options?.returnObjects)
return [`${key}-feature-1`, `${key}-feature-2`]This functionality is now missing from the centralized mock. While no tests in this PR use returnObjects, other existing tests in the codebase do (e.g., explore/create-app-modal/index.spec.tsx, billing/pricing/index.spec.tsx). Those tests still have local mocks, but if they're updated later to use the centralized mock, they will break.
Suggested fix:
| export function createTFunction(translations: TranslationMap, defaultNs?: string) { | |
| return (key: string, options?: Record<string, unknown>) => { | |
| // Check if translations[key] is not undefined | |
| if (translations[key] !== undefined) | |
| return translations[key] | |
| // Get namespace from options or use defaultNs | |
| const ns = (options?.ns as string | undefined) ?? defaultNs | |
| // Create fullKey by concatenating namespace and key with a dot | |
| const fullKey = ns ? `${ns}.${key}` : key | |
| // Check if translations[fullKey] is not undefined | |
| if (translations[fullKey] !== undefined) | |
| return translations[fullKey] | |
| // Create a copy of options object | |
| const params = { ...options } | |
| // Delete the ns property from params | |
| delete params.ns | |
| // Delete the returnObjects property from params | |
| delete params.returnObjects | |
| // Create suffix by stringifying params if there are any keys | |
| const suffix = Object.keys(params).length > 0 ? `:${JSON.stringify(params)}` : '' | |
| // Return the fullKey concatenated with suffix | |
| return `${fullKey}${suffix}` | |
| } | |
| export function createTFunction(translations: TranslationMap, defaultNs?: string) { | |
| return (key: string, options?: Record<string, unknown>) => { | |
| // Handle returnObjects option | |
| if (options?.returnObjects) | |
| return [`${key}-feature-1`, `${key}-feature-2`] | |
| // Check if translations[key] is not undefined | |
| if (translations[key] !== undefined) | |
| return translations[key] | |
| // Get namespace from options or use defaultNs | |
| const ns = (options?.ns as string | undefined) ?? defaultNs | |
| // Create fullKey by concatenating namespace and key with a dot | |
| const fullKey = ns ? `${ns}.${key}` : key | |
| // Check if translations[fullKey] is not undefined | |
| if (translations[fullKey] !== undefined) | |
| return translations[fullKey] | |
| // Create a copy of options object | |
| const params = { ...options } | |
| // Delete the ns property from params | |
| delete params.ns | |
| // Delete the returnObjects property from params | |
| delete params.returnObjects | |
| // Create suffix by stringifying params if there are any keys | |
| const suffix = Object.keys(params).length > 0 ? `:${JSON.stringify(params)}` : '' | |
| // Return the fullKey concatenated with suffix | |
| return `${fullKey}${suffix}` | |
| } | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: web/test/i18n-mock.ts
Line: 10:35
Comment:
The `createTFunction` deletes the `returnObjects` property but doesn't handle it. The old global mock in `vitest.setup.ts` had this logic:
```typescript
if (options?.returnObjects)
return [`${key}-feature-1`, `${key}-feature-2`]
```
This functionality is now missing from the centralized mock. While no tests in this PR use `returnObjects`, other existing tests in the codebase do (e.g., `explore/create-app-modal/index.spec.tsx`, `billing/pricing/index.spec.tsx`). Those tests still have local mocks, but if they're updated later to use the centralized mock, they will break.
**Suggested fix:**
```suggestion
export function createTFunction(translations: TranslationMap, defaultNs?: string) {
return (key: string, options?: Record<string, unknown>) => {
// Handle returnObjects option
if (options?.returnObjects)
return [`${key}-feature-1`, `${key}-feature-2`]
// Check if translations[key] is not undefined
if (translations[key] !== undefined)
return translations[key]
// Get namespace from options or use defaultNs
const ns = (options?.ns as string | undefined) ?? defaultNs
// Create fullKey by concatenating namespace and key with a dot
const fullKey = ns ? `${ns}.${key}` : key
// Check if translations[fullKey] is not undefined
if (translations[fullKey] !== undefined)
return translations[fullKey]
// Create a copy of options object
const params = { ...options }
// Delete the ns property from params
delete params.ns
// Delete the returnObjects property from params
delete params.returnObjects
// Create suffix by stringifying params if there are any keys
const suffix = Object.keys(params).length > 0 ? `:${JSON.stringify(params)}` : ''
// Return the fullKey concatenated with suffix
return `${fullKey}${suffix}`
}
}
```
How can I resolve this? If you propose a fix, please make it concise.| // mock react-i18next | ||
| vi.mock('react-i18next', async () => { | ||
| const actual = await vi.importActual<typeof import('react-i18next')>('react-i18next') | ||
| const { createReactI18nextMock } = await import('./test/i18n-mock') | ||
| return { | ||
| ...actual, | ||
| useTranslation: (defaultNs?: string) => ({ | ||
| t: (key: string, options?: Record<string, unknown>) => { | ||
| if (options?.returnObjects) | ||
| return [`${key}-feature-1`, `${key}-feature-2`] | ||
| const ns = options?.ns ?? defaultNs | ||
| if (options || ns) { | ||
| const { ns: _ns, ...rest } = options ?? {} | ||
| const prefix = ns ? `${ns}.` : '' | ||
| const suffix = Object.keys(rest).length > 0 ? `:${JSON.stringify(rest)}` : '' | ||
| return `${prefix}${key}${suffix}` | ||
| } | ||
| return key | ||
| }, | ||
| i18n: { | ||
| language: 'en', | ||
| changeLanguage: vi.fn(), | ||
| }, | ||
| }), | ||
| ...createReactI18nextMock(), | ||
| } | ||
| }) |
There was a problem hiding this comment.
Critical issue: The global mock only provides useTranslation and Trans from createReactI18nextMock(), but the documentation in testing.md and mocking.md claims it also provides:
useMixedTranslation(from@/app/components/plugins/marketplace/hooks)useGetLanguage(from@/context/i18n)
These are NOT mocked here, which causes test failures for components that use them (e.g., plugins/card/index.spec.tsx and plugins/plugin-mutation-model/index.spec.tsx removed their local mocks for these hooks in this PR).
Required fix: Add the missing global mocks after the react-i18next mock:
| // mock react-i18next | |
| vi.mock('react-i18next', async () => { | |
| const actual = await vi.importActual<typeof import('react-i18next')>('react-i18next') | |
| const { createReactI18nextMock } = await import('./test/i18n-mock') | |
| return { | |
| ...actual, | |
| useTranslation: (defaultNs?: string) => ({ | |
| t: (key: string, options?: Record<string, unknown>) => { | |
| if (options?.returnObjects) | |
| return [`${key}-feature-1`, `${key}-feature-2`] | |
| const ns = options?.ns ?? defaultNs | |
| if (options || ns) { | |
| const { ns: _ns, ...rest } = options ?? {} | |
| const prefix = ns ? `${ns}.` : '' | |
| const suffix = Object.keys(rest).length > 0 ? `:${JSON.stringify(rest)}` : '' | |
| return `${prefix}${key}${suffix}` | |
| } | |
| return key | |
| }, | |
| i18n: { | |
| language: 'en', | |
| changeLanguage: vi.fn(), | |
| }, | |
| }), | |
| ...createReactI18nextMock(), | |
| } | |
| }) | |
| // mock react-i18next | |
| vi.mock('react-i18next', async () => { | |
| const actual = await vi.importActual<typeof import('react-i18next')>('react-i18next') | |
| const { createReactI18nextMock } = await import('./test/i18n-mock') | |
| return { | |
| ...actual, | |
| ...createReactI18nextMock(), | |
| } | |
| }) | |
| // mock useMixedTranslation hook | |
| vi.mock('@/app/components/plugins/marketplace/hooks', async () => { | |
| const actual = await vi.importActual<typeof import('@/app/components/plugins/marketplace/hooks')>('@/app/components/plugins/marketplace/hooks') | |
| const { createMixedTranslationMock } = await import('./test/i18n-mock') | |
| return { | |
| ...actual, | |
| ...createMixedTranslationMock(), | |
| } | |
| }) | |
| // mock useGetLanguage hook | |
| vi.mock('@/context/i18n', async () => { | |
| const actual = await vi.importActual<typeof import('@/context/i18n')>('@/context/i18n') | |
| const { createUseGetLanguageMock } = await import('./test/i18n-mock') | |
| return { | |
| ...actual, | |
| ...createUseGetLanguageMock(), | |
| } | |
| }) |
Prompt To Fix With AI
This is a comment left during a code review.
Path: web/vitest.setup.ts
Line: 88:96
Comment:
**Critical issue:** The global mock only provides `useTranslation` and `Trans` from `createReactI18nextMock()`, but the documentation in `testing.md` and `mocking.md` claims it also provides:
- `useMixedTranslation` (from `@/app/components/plugins/marketplace/hooks`)
- `useGetLanguage` (from `@/context/i18n`)
These are NOT mocked here, which causes test failures for components that use them (e.g., `plugins/card/index.spec.tsx` and `plugins/plugin-mutation-model/index.spec.tsx` removed their local mocks for these hooks in this PR).
**Required fix:** Add the missing global mocks after the `react-i18next` mock:
```suggestion
// mock react-i18next
vi.mock('react-i18next', async () => {
const actual = await vi.importActual<typeof import('react-i18next')>('react-i18next')
const { createReactI18nextMock } = await import('./test/i18n-mock')
return {
...actual,
...createReactI18nextMock(),
}
})
// mock useMixedTranslation hook
vi.mock('@/app/components/plugins/marketplace/hooks', async () => {
const actual = await vi.importActual<typeof import('@/app/components/plugins/marketplace/hooks')>('@/app/components/plugins/marketplace/hooks')
const { createMixedTranslationMock } = await import('./test/i18n-mock')
return {
...actual,
...createMixedTranslationMock(),
}
})
// mock useGetLanguage hook
vi.mock('@/context/i18n', async () => {
const actual = await vi.importActual<typeof import('@/context/i18n')>('@/context/i18n')
const { createUseGetLanguageMock } = await import('./test/i18n-mock')
return {
...actual,
...createUseGetLanguageMock(),
}
})
```
How can I resolve this? If you propose a fix, please make it concise.| // Mock External Dependencies Only | ||
| // ================================ | ||
|
|
There was a problem hiding this comment.
Test will fail: Same issue as plugins/card/index.spec.tsx - the PluginMutationModal component uses useMixedTranslation and useGetLanguage, but the local mocks were removed and these hooks are not mocked in the global setup.
Fix: Either restore the local mocks OR add them to the global setup in vitest.setup.ts (see comment on that file).
Prompt To Fix With AI
This is a comment left during a code review.
Path: web/app/components/plugins/plugin-mutation-model/index.spec.tsx
Line: 9:11
Comment:
**Test will fail:** Same issue as `plugins/card/index.spec.tsx` - the PluginMutationModal component uses `useMixedTranslation` and `useGetLanguage`, but the local mocks were removed and these hooks are not mocked in the global setup.
**Fix:** Either restore the local mocks OR add them to the global setup in `vitest.setup.ts` (see comment on that file).
How can I resolve this? If you propose a fix, please make it concise.| 1. **i18n**: Uses global mock in `web/vitest.setup.ts` (auto-loaded by Vitest setup) | ||
|
|
||
| The global mock returns translation keys as-is. For custom translations, override: | ||
| The global mock provides: | ||
|
|
||
| - `useTranslation` - returns translation keys with namespace prefix | ||
| - `Trans` component - renders i18nKey and components | ||
| - `useMixedTranslation` (from `@/app/components/plugins/marketplace/hooks`) | ||
| - `useGetLanguage` (from `@/context/i18n`) - returns `'en-US'` | ||
|
|
There was a problem hiding this comment.
Documentation inaccuracy: This section claims the global mock provides useMixedTranslation and useGetLanguage, but these are NOT actually mocked in web/vitest.setup.ts. The global setup only mocks react-i18next (which provides useTranslation and Trans).
This misleading documentation caused test files like plugins/card/index.spec.tsx and plugins/plugin-mutation-model/index.spec.tsx to remove their local mocks for these hooks, which will cause test failures.
Fix: Either update the documentation to reflect what's actually mocked, OR add the missing mocks to vitest.setup.ts.
Prompt To Fix With AI
This is a comment left during a code review.
Path: web/testing/testing.md
Line: 330:338
Comment:
**Documentation inaccuracy:** This section claims the global mock provides `useMixedTranslation` and `useGetLanguage`, but these are NOT actually mocked in `web/vitest.setup.ts`. The global setup only mocks `react-i18next` (which provides `useTranslation` and `Trans`).
This misleading documentation caused test files like `plugins/card/index.spec.tsx` and `plugins/plugin-mutation-model/index.spec.tsx` to remove their local mocks for these hooks, which will cause test failures.
**Fix:** Either update the documentation to reflect what's actually mocked, OR add the missing mocks to `vitest.setup.ts`.
How can I resolve this? If you propose a fix, please make it concise.| ### 1. i18n (Auto-loaded via Global Mock) | ||
|
|
||
| A global mock is defined in `web/vitest.setup.ts` and is auto-loaded by Vitest setup. | ||
| **No explicit mock needed** for most tests - it returns translation keys as-is. | ||
|
|
||
| For tests requiring custom translations, override the mock: | ||
| The global mock provides: | ||
|
|
||
| - `useTranslation` - returns translation keys with namespace prefix | ||
| - `Trans` component - renders i18nKey and components | ||
| - `useMixedTranslation` (from `@/app/components/plugins/marketplace/hooks`) | ||
| - `useGetLanguage` (from `@/context/i18n`) - returns `'en-US'` | ||
|
|
There was a problem hiding this comment.
Documentation inaccuracy: Same issue as testing.md - this documentation claims the global mock provides useMixedTranslation and useGetLanguage, but these are NOT actually mocked in web/vitest.setup.ts.
Fix: Either update the documentation to reflect what's actually mocked, OR add the missing mocks to vitest.setup.ts.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .claude/skills/frontend-testing/references/mocking.md
Line: 52:62
Comment:
**Documentation inaccuracy:** Same issue as `testing.md` - this documentation claims the global mock provides `useMixedTranslation` and `useGetLanguage`, but these are NOT actually mocked in `web/vitest.setup.ts`.
**Fix:** Either update the documentation to reflect what's actually mocked, OR add the missing mocks to `vitest.setup.ts`.
How can I resolve this? If you propose a fix, please make it concise.| // Mock External Dependencies Only | ||
| // ================================ | ||
|
|
There was a problem hiding this comment.
Test will fail: The Card component uses useMixedTranslation and useGetLanguage (imported in the actual component), but this test file removed the local mocks for these hooks. The global mock in vitest.setup.ts does NOT provide mocks for these hooks, so tests will fail when trying to render the Card component.
Fix: Either restore the local mocks that were removed in this PR OR add global mocks for these hooks in vitest.setup.ts (see comment on that file).
Prompt To Fix With AI
This is a comment left during a code review.
Path: web/app/components/plugins/card/index.spec.tsx
Line: 21:23
Comment:
**Test will fail:** The Card component uses `useMixedTranslation` and `useGetLanguage` (imported in the actual component), but this test file removed the local mocks for these hooks. The global mock in `vitest.setup.ts` does NOT provide mocks for these hooks, so tests will fail when trying to render the Card component.
**Fix:** Either restore the local mocks that were removed in this PR OR add global mocks for these hooks in `vitest.setup.ts` (see comment on that file).
How can I resolve this? If you propose a fix, please make it concise.
Benchmark PR from qodo-benchmark#129