Skip to content

Commit 371503a

Browse files
authored
Merge pull request #439 from objectstack-ai/copilot/improve-test-coverage-i18n
2 parents cafc651 + 3ffdb40 commit 371503a

18 files changed

Lines changed: 2467 additions & 11 deletions

File tree

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "@object-ui/core",
33
"version": "0.5.0",
44
"type": "module",
5+
"sideEffects": false,
56
"license": "MIT",
67
"description": "Core logic, types, and validation for Object UI. Zero React dependencies.",
78
"homepage": "https://www.objectui.org",

packages/i18n/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "@object-ui/i18n",
33
"version": "0.5.0",
44
"type": "module",
5+
"sideEffects": false,
56
"license": "MIT",
67
"description": "Internationalization (i18n) support for Object UI with 10+ language packs, RTL layout, and date/currency formatting.",
78
"homepage": "https://www.objectui.org",
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { renderHook, waitFor, act } from '@testing-library/react';
3+
import React from 'react';
4+
import { I18nProvider, useObjectTranslation, useI18nContext } from '../provider';
5+
import { createI18n } from '../i18n';
6+
7+
describe('I18nProvider', () => {
8+
it('creates i18n instance from config', () => {
9+
const wrapper = ({ children }: { children: React.ReactNode }) =>
10+
React.createElement(I18nProvider, { config: { defaultLanguage: 'en', detectBrowserLanguage: false } }, children);
11+
12+
const { result } = renderHook(() => useObjectTranslation(), { wrapper });
13+
14+
expect(result.current.i18n).toBeDefined();
15+
expect(result.current.language).toBe('en');
16+
});
17+
18+
it('accepts pre-created instance', () => {
19+
const instance = createI18n({ defaultLanguage: 'fr', detectBrowserLanguage: false });
20+
21+
const wrapper = ({ children }: { children: React.ReactNode }) =>
22+
React.createElement(I18nProvider, { instance }, children);
23+
24+
const { result } = renderHook(() => useObjectTranslation(), { wrapper });
25+
26+
expect(result.current.i18n).toBeDefined();
27+
expect(result.current.language).toBe('fr');
28+
});
29+
});
30+
31+
describe('useObjectTranslation', () => {
32+
const wrapper = ({ children }: { children: React.ReactNode }) =>
33+
React.createElement(I18nProvider, { config: { defaultLanguage: 'en', detectBrowserLanguage: false } }, children);
34+
35+
it('returns t, language, changeLanguage, direction, i18n', () => {
36+
const { result } = renderHook(() => useObjectTranslation(), { wrapper });
37+
38+
expect(result.current.t).toBeTypeOf('function');
39+
expect(result.current.language).toBeTypeOf('string');
40+
expect(result.current.changeLanguage).toBeTypeOf('function');
41+
expect(result.current.direction).toBeTypeOf('string');
42+
expect(result.current.i18n).toBeDefined();
43+
});
44+
45+
it('translates keys correctly', () => {
46+
const { result } = renderHook(() => useObjectTranslation(), { wrapper });
47+
48+
expect(result.current.t('common.save')).toBe('Save');
49+
expect(result.current.t('common.cancel')).toBe('Cancel');
50+
expect(result.current.t('common.delete')).toBe('Delete');
51+
});
52+
53+
it('returns en as default language', () => {
54+
const { result } = renderHook(() => useObjectTranslation(), { wrapper });
55+
56+
expect(result.current.language).toBe('en');
57+
});
58+
59+
it('works with Chinese language', () => {
60+
const zhWrapper = ({ children }: { children: React.ReactNode }) =>
61+
React.createElement(I18nProvider, { config: { defaultLanguage: 'zh', detectBrowserLanguage: false } }, children);
62+
63+
const { result } = renderHook(() => useObjectTranslation(), { wrapper: zhWrapper });
64+
65+
expect(result.current.language).toBe('zh');
66+
expect(result.current.t('common.save')).toBe('保存');
67+
expect(result.current.t('common.cancel')).toBe('取消');
68+
});
69+
70+
it('changeLanguage updates language', async () => {
71+
const { result } = renderHook(() => useObjectTranslation(), { wrapper });
72+
73+
expect(result.current.language).toBe('en');
74+
75+
await act(async () => {
76+
await result.current.changeLanguage('zh');
77+
});
78+
79+
await waitFor(() => {
80+
expect(result.current.language).toBe('zh');
81+
});
82+
});
83+
84+
it('returns RTL direction for Arabic', () => {
85+
const arWrapper = ({ children }: { children: React.ReactNode }) =>
86+
React.createElement(I18nProvider, { config: { defaultLanguage: 'ar', detectBrowserLanguage: false } }, children);
87+
88+
const { result } = renderHook(() => useObjectTranslation(), { wrapper: arWrapper });
89+
90+
expect(result.current.direction).toBe('rtl');
91+
});
92+
});
93+
94+
describe('useI18nContext', () => {
95+
it('throws when used outside provider', () => {
96+
// Suppress console.error from React for the expected error
97+
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
98+
99+
expect(() => {
100+
renderHook(() => useI18nContext());
101+
}).toThrow('useI18nContext must be used within an I18nProvider');
102+
103+
spy.mockRestore();
104+
});
105+
106+
it('returns context inside provider', () => {
107+
const wrapper = ({ children }: { children: React.ReactNode }) =>
108+
React.createElement(I18nProvider, { config: { defaultLanguage: 'en', detectBrowserLanguage: false } }, children);
109+
110+
const { result } = renderHook(() => useI18nContext(), { wrapper });
111+
112+
expect(result.current.language).toBe('en');
113+
expect(result.current.changeLanguage).toBeTypeOf('function');
114+
expect(result.current.direction).toBe('ltr');
115+
expect(result.current.i18n).toBeDefined();
116+
});
117+
});

packages/layout/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "@object-ui/layout",
33
"version": "0.5.0",
44
"type": "module",
5+
"sideEffects": false,
56
"main": "dist/index.umd.cjs",
67
"module": "dist/index.js",
78
"types": "dist/index.d.ts",

0 commit comments

Comments
 (0)