Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@object-ui/core",
"version": "0.5.0",
"type": "module",
"sideEffects": false,
"license": "MIT",
"description": "Core logic, types, and validation for Object UI. Zero React dependencies.",
"homepage": "https://www.objectui.org",
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@object-ui/i18n",
"version": "0.5.0",
"type": "module",
"sideEffects": false,
"license": "MIT",
"description": "Internationalization (i18n) support for Object UI with 10+ language packs, RTL layout, and date/currency formatting.",
"homepage": "https://www.objectui.org",
Expand Down
117 changes: 117 additions & 0 deletions packages/i18n/src/__tests__/provider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { describe, it, expect, vi } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import React from 'react';
import { I18nProvider, useObjectTranslation, useI18nContext } from '../provider';
import { createI18n } from '../i18n';

describe('I18nProvider', () => {
it('creates i18n instance from config', () => {
const wrapper = ({ children }: { children: React.ReactNode }) =>
React.createElement(I18nProvider, { config: { defaultLanguage: 'en', detectBrowserLanguage: false } }, children);

const { result } = renderHook(() => useObjectTranslation(), { wrapper });

expect(result.current.i18n).toBeDefined();
expect(result.current.language).toBe('en');
});

it('accepts pre-created instance', () => {
const instance = createI18n({ defaultLanguage: 'fr', detectBrowserLanguage: false });

const wrapper = ({ children }: { children: React.ReactNode }) =>
React.createElement(I18nProvider, { instance }, children);

const { result } = renderHook(() => useObjectTranslation(), { wrapper });

expect(result.current.i18n).toBeDefined();
expect(result.current.language).toBe('fr');
});
});

describe('useObjectTranslation', () => {
const wrapper = ({ children }: { children: React.ReactNode }) =>
React.createElement(I18nProvider, { config: { defaultLanguage: 'en', detectBrowserLanguage: false } }, children);

it('returns t, language, changeLanguage, direction, i18n', () => {
const { result } = renderHook(() => useObjectTranslation(), { wrapper });

expect(result.current.t).toBeTypeOf('function');
expect(result.current.language).toBeTypeOf('string');
expect(result.current.changeLanguage).toBeTypeOf('function');
expect(result.current.direction).toBeTypeOf('string');
expect(result.current.i18n).toBeDefined();
});

it('translates keys correctly', () => {
const { result } = renderHook(() => useObjectTranslation(), { wrapper });

expect(result.current.t('common.save')).toBe('Save');
expect(result.current.t('common.cancel')).toBe('Cancel');
expect(result.current.t('common.delete')).toBe('Delete');
});

it('returns en as default language', () => {
const { result } = renderHook(() => useObjectTranslation(), { wrapper });

expect(result.current.language).toBe('en');
});

it('works with Chinese language', () => {
const zhWrapper = ({ children }: { children: React.ReactNode }) =>
React.createElement(I18nProvider, { config: { defaultLanguage: 'zh', detectBrowserLanguage: false } }, children);

const { result } = renderHook(() => useObjectTranslation(), { wrapper: zhWrapper });

expect(result.current.language).toBe('zh');
expect(result.current.t('common.save')).toBe('保存');
expect(result.current.t('common.cancel')).toBe('取消');
});

it('changeLanguage updates language', async () => {
const { result } = renderHook(() => useObjectTranslation(), { wrapper });

expect(result.current.language).toBe('en');

await act(async () => {
await result.current.changeLanguage('zh');
});

await waitFor(() => {
expect(result.current.language).toBe('zh');
});
});

it('returns RTL direction for Arabic', () => {
const arWrapper = ({ children }: { children: React.ReactNode }) =>
React.createElement(I18nProvider, { config: { defaultLanguage: 'ar', detectBrowserLanguage: false } }, children);

const { result } = renderHook(() => useObjectTranslation(), { wrapper: arWrapper });

expect(result.current.direction).toBe('rtl');
});
});

describe('useI18nContext', () => {
it('throws when used outside provider', () => {
// Suppress console.error from React for the expected error
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});

expect(() => {
renderHook(() => useI18nContext());
}).toThrow('useI18nContext must be used within an I18nProvider');

spy.mockRestore();
});

it('returns context inside provider', () => {
const wrapper = ({ children }: { children: React.ReactNode }) =>
React.createElement(I18nProvider, { config: { defaultLanguage: 'en', detectBrowserLanguage: false } }, children);

const { result } = renderHook(() => useI18nContext(), { wrapper });

expect(result.current.language).toBe('en');
expect(result.current.changeLanguage).toBeTypeOf('function');
expect(result.current.direction).toBe('ltr');
expect(result.current.i18n).toBeDefined();
});
});
1 change: 1 addition & 0 deletions packages/layout/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@object-ui/layout",
"version": "0.5.0",
"type": "module",
"sideEffects": false,
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting sideEffects: false is incorrect for this package. The entry point src/index.ts has module-level side effects (calls registerLayout() at lines 53-57 to register components in ComponentRegistry). If bundlers tree-shake this module due to sideEffects: false, the layout components will not be registered and will fail at runtime. Either remove this field or set it to true, or move the registration call out of the module-level scope.

Suggested change
"sideEffects": false,
"sideEffects": true,

Copilot uses AI. Check for mistakes.
"main": "dist/index.umd.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
Loading
Loading