test: unify i18next mocks into centralized helpers - #56
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.
There was a problem hiding this comment.
Pull request overview
This PR centralizes i18next mocking by introducing reusable helper functions in web/test/i18n-mock.ts and updating the global mock in web/vitest.setup.ts to use these helpers. The goal is to reduce code duplication across test files and standardize i18n mocking patterns.
Changes:
- Created centralized i18n mock helpers (
createTFunction,createUseTranslationMock,createTransMock,createReactI18nextMock) - Updated global mock in
vitest.setup.tsto use the new helper - Removed local i18n mocks from 10+ test files, relying on global mock or new helpers
- Updated documentation and test templates to reference the centralized approach
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| web/vitest.setup.ts | Updated global react-i18next mock to use centralized helper |
| web/test/i18n-mock.ts | New file with centralized i18n mock helper functions |
| web/testing/testing.md | Updated testing guidelines to document centralized mock usage |
| .claude/skills/frontend-testing/references/mocking.md | Updated mocking reference documentation |
| .claude/skills/frontend-testing/assets/component-test.template.tsx | Updated test template with new mocking approach |
| web/app/components/plugins/plugin-mutation-model/index.spec.tsx | Removed local i18n mock (now uses global) |
| web/app/components/plugins/plugin-detail-panel/subscription-list/edit/index.spec.tsx | Removed local i18n mock (now uses global) |
| web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.spec.tsx | Removed local i18n mock (now uses global) |
| web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.spec.tsx | Removed local i18n mock (now uses global) |
| web/app/components/plugins/marketplace/index.spec.tsx | Updated i18next-config mock and test assertions for namespace handling |
| web/app/components/plugins/install-plugin/install-from-local-package/steps/uploading.spec.tsx | Removed local i18n mock (now uses global) |
| web/app/components/plugins/install-plugin/install-from-local-package/steps/install.spec.tsx | Updated to use centralized helper with custom Trans override |
| web/app/components/plugins/card/index.spec.tsx | Removed local i18n mocks including useMixedTranslation and useGetLanguage |
| web/app/components/datasets/documents/create-from-pipeline/processing/index.spec.tsx | Removed local i18n mock (now uses global) |
| web/app/components/datasets/create/index.spec.tsx | Removed local i18n mock (now uses global) |
| web/app/components/billing/pricing/footer.spec.tsx | Removed local i18n mock and translation state variable |
| web/app/components/base/input/index.spec.tsx | Updated to use centralized helper with custom translations |
| web/app/components/base/input-with-copy/index.spec.tsx | Updated to use centralized helper with custom translations |
| web/app/components/base/inline-delete-confirm/index.spec.tsx | Updated to use centralized helper with custom translations |
| web/app/components/app/configuration/config/config-audio.spec.tsx | Removed local i18n mock (now uses global) |
| web/app/components/app/configuration/config/agent-setting-button.spec.tsx | Removed local i18n mock (now uses global) |
| // The global mock provides: useTranslation, Trans, useMixedTranslation, useGetLanguage | ||
| // No explicit mock needed for most tests |
There was a problem hiding this comment.
The comment incorrectly states that the global mock provides useMixedTranslation and useGetLanguage. Looking at web/vitest.setup.ts, the global mock only covers react-i18next exports (useTranslation, Trans, etc.). The hooks useMixedTranslation (from @/app/components/plugins/marketplace/hooks) and useGetLanguage (from @/context/i18n) are not mocked globally and must be mocked locally in test files that use components which depend on these hooks.
| // The global mock provides: useTranslation, Trans, useMixedTranslation, useGetLanguage | |
| // No explicit mock needed for most tests | |
| // The global mock provides core react-i18next exports like useTranslation and Trans | |
| // Hooks like useMixedTranslation and useGetLanguage are NOT globally mocked; mock them locally when needed |
|
|
||
| /** | ||
| * Create a t function with optional custom translations | ||
| * Checks translations[key] first, then translations[ns.key], then returns ns.key as fallback |
There was a problem hiding this comment.
The comment incorrectly describes the lookup order. The code checks translations[fullKey] first (which is ns.key when namespace is present), then translations[key] without namespace. The comment says "Checks translations[key] first, then translations[ns.key]" which is the opposite order. The comment should be corrected to: "Checks translations[ns.key] first, then translations[key], then returns ns.key as fallback"
| * Checks translations[key] first, then translations[ns.key], then returns ns.key as fallback | |
| * Checks translations[ns.key] first, then translations[key], then returns ns.key as fallback |
| export function createTFunction(translations: TranslationMap, defaultNs?: string) { | ||
| return (key: string, options?: Record<string, unknown>) => { | ||
| const ns = (options?.ns as string | undefined) ?? defaultNs | ||
| const fullKey = ns ? `${ns}.${key}` : key | ||
|
|
||
| // Check custom translations with namespace first | ||
| if (translations[fullKey] !== undefined) | ||
| return translations[fullKey] | ||
|
|
||
| // Check custom translations without namespace | ||
| if (translations[key] !== undefined) | ||
| return translations[key] | ||
|
|
||
| // Serialize params (excluding ns) for test assertions | ||
| const params = { ...options } | ||
| delete params.ns | ||
| const suffix = Object.keys(params).length > 0 ? `:${JSON.stringify(params)}` : '' | ||
| return `${fullKey}${suffix}` | ||
| } | ||
| } |
There was a problem hiding this comment.
The createTFunction does not handle the returnObjects option. The old global mock implementation supported returnObjects by returning an array when this option was present. This functionality is used in several test files (e.g., web/app/components/app/overview/settings/index.spec.tsx, web/app/components/billing/pricing/index.spec.tsx). Without this support, tests that rely on returnObjects will fail or behave incorrectly.
The function should check for options?.returnObjects and return an array from the custom translations if available, or a default array pattern if not found in translations.
| export function createUseTranslationMock(translations: TranslationMap = {}) { | ||
| return { | ||
| useTranslation: () => ({ | ||
| t: createTFunction(translations), | ||
| i18n: { | ||
| language: 'en', | ||
| changeLanguage: vi.fn(), | ||
| }, | ||
| }), |
There was a problem hiding this comment.
The useTranslation mock function should accept a defaultNs parameter to match the react-i18next API. The old global mock implementation accepted this parameter: useTranslation: (defaultNs?: string) => ({...}). This allows tests to specify a default namespace that will be used when no namespace is explicitly provided in translation calls. Without this, tests that pass a default namespace to useTranslation will not work correctly.
| Trans: ({ i18nKey, children }: { | ||
| i18nKey: string | ||
| children?: React.ReactNode | ||
| }) => { | ||
| const text = translations[i18nKey] ?? i18nKey | ||
| return React.createElement('span', { 'data-i18n-key': i18nKey }, children ?? text) |
There was a problem hiding this comment.
The Trans component mock is missing support for the components prop, which is commonly used in react-i18next for rendering specific parts of translated text with custom components. The test file web/app/components/plugins/install-plugin/install-from-local-package/steps/install.spec.tsx explicitly overrides this mock to add components support. The base mock should handle this prop to provide more complete functionality out of the box.
| Trans: ({ i18nKey, children }: { | |
| i18nKey: string | |
| children?: React.ReactNode | |
| }) => { | |
| const text = translations[i18nKey] ?? i18nKey | |
| return React.createElement('span', { 'data-i18n-key': i18nKey }, children ?? text) | |
| Trans: ({ i18nKey, children, components }: { | |
| i18nKey: string | |
| children?: React.ReactNode | |
| components?: React.ReactNode | React.ReactNode[] | |
| }) => { | |
| const text = translations[i18nKey] ?? i18nKey | |
| const content = components != null && children == null ? components : (children ?? text) | |
| return React.createElement('span', { 'data-i18n-key': i18nKey }, content) |
| // Mock External Dependencies Only | ||
| // ================================ | ||
|
|
There was a problem hiding this comment.
The mocks for useMixedTranslation and useGetLanguage were removed from this test file, but these hooks are actually used by the Card component. The component imports useMixedTranslation from @/app/components/plugins/marketplace/hooks (line 56 of index.tsx) and useGetLanguage from @/context/i18n (line 54 of index.tsx). These hooks are not mocked in the global vitest.setup.ts file, so removing these mocks will cause tests to fail when they try to render the Card component.
| - `useMixedTranslation` (from `@/app/components/plugins/marketplace/hooks`) | ||
| - `useGetLanguage` (from `@/context/i18n`) - returns `'en-US'` | ||
|
|
||
| **Default behavior**: Most tests should use the global mock (no local override needed). | ||
|
|
There was a problem hiding this comment.
The documentation incorrectly states that the global mock provides useMixedTranslation and useGetLanguage. Looking at web/vitest.setup.ts, the global mock only covers react-i18next exports (useTranslation, Trans, etc.). The hooks useMixedTranslation (from @/app/components/plugins/marketplace/hooks) and useGetLanguage (from @/context/i18n) are not mocked globally and must be mocked locally in test files that use components which depend on these hooks.
| - `useMixedTranslation` (from `@/app/components/plugins/marketplace/hooks`) | |
| - `useGetLanguage` (from `@/context/i18n`) - returns `'en-US'` | |
| **Default behavior**: Most tests should use the global mock (no local override needed). | |
| **Important**: App-specific hooks like: | |
| - `useMixedTranslation` (from `@/app/components/plugins/marketplace/hooks`) | |
| - `useGetLanguage` (from `@/context/i18n`) | |
| are **not** part of the global mock and must be mocked **locally** in each test file that uses components depending on them, for example: | |
| ```typescript | |
| vi.mock('@/context/i18n', () => ({ | |
| useGetLanguage: () => 'en-US', | |
| })) | |
| vi.mock('@/app/components/plugins/marketplace/hooks', () => ({ | |
| useMixedTranslation: () => ({ | |
| t: (key: string) => key, | |
| }), | |
| })) |
Default behavior: Most tests should use the global mock (no local override needed) for react-i18next.
| - `useMixedTranslation` (from `@/app/components/plugins/marketplace/hooks`) | ||
| - `useGetLanguage` (from `@/context/i18n`) - returns `'en-US'` | ||
|
|
There was a problem hiding this comment.
The documentation incorrectly states that the global mock provides useMixedTranslation and useGetLanguage. Looking at web/vitest.setup.ts, the global mock only covers react-i18next exports (useTranslation, Trans, etc.). The hooks useMixedTranslation (from @/app/components/plugins/marketplace/hooks) and useGetLanguage (from @/context/i18n) are not mocked globally and must be mocked locally in test files that use components which depend on these hooks.
| - `useMixedTranslation` (from `@/app/components/plugins/marketplace/hooks`) | |
| - `useGetLanguage` (from `@/context/i18n`) - returns `'en-US'` | |
| **Note:** `useMixedTranslation` (from `@/app/components/plugins/marketplace/hooks`) and `useGetLanguage` (from `@/context/i18n`) are **not** mocked globally. Mock them locally in test files that render components depending on these hooks. |
Benchmark PR from qodo-benchmark#437