Files
goodgo-platform/apps/web/messages/__tests__/messages.spec.ts
Ho Ngoc Hai 0924f0cb9b test(web): add middleware + i18n + messages test suites (GOO-60)
Cover frontend middleware auth/locale logic, i18n config/routing/request/navigation,
and vi/en message parity — 91 new tests across 6 files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 10:47:30 +07:00

223 lines
8.0 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import viMessages from '../../messages/vi.json';
import enMessages from '../../messages/en.json';
describe('locale messages', () => {
describe('structural parity between vi.json and en.json', () => {
function getKeys(obj: Record<string, unknown>, prefix = ''): string[] {
const keys: string[] = [];
for (const key of Object.keys(obj)) {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (
typeof obj[key] === 'object' &&
obj[key] !== null &&
!Array.isArray(obj[key])
) {
keys.push(
...getKeys(obj[key] as Record<string, unknown>, fullKey),
);
} else {
keys.push(fullKey);
}
}
return keys.sort();
}
it('vi.json and en.json have the same top-level namespaces', () => {
const viNamespaces = Object.keys(viMessages).sort();
const enNamespaces = Object.keys(enMessages).sort();
expect(viNamespaces).toEqual(enNamespaces);
});
it('vi.json and en.json have the exact same key structure', () => {
const viKeys = getKeys(viMessages as unknown as Record<string, unknown>);
const enKeys = getKeys(enMessages as unknown as Record<string, unknown>);
expect(viKeys).toEqual(enKeys);
});
it('no keys are missing from en.json', () => {
const viKeys = new Set(
getKeys(viMessages as unknown as Record<string, unknown>),
);
const enKeys = new Set(
getKeys(enMessages as unknown as Record<string, unknown>),
);
const missingInEn = [...viKeys].filter((k) => !enKeys.has(k));
expect(missingInEn).toEqual([]);
});
it('no keys are missing from vi.json', () => {
const viKeys = new Set(
getKeys(viMessages as unknown as Record<string, unknown>),
);
const enKeys = new Set(
getKeys(enMessages as unknown as Record<string, unknown>),
);
const missingInVi = [...enKeys].filter((k) => !viKeys.has(k));
expect(missingInVi).toEqual([]);
});
});
describe('Vietnamese (vi.json) content', () => {
it('has a metadata namespace with title', () => {
expect(viMessages.metadata).toBeDefined();
expect(viMessages.metadata.title).toBe(
'GoodGo — Nền tảng Bất động sản Việt Nam',
);
});
it('has common namespace with Vietnamese translations', () => {
expect(viMessages.common.loading).toBe('Đang tải...');
expect(viMessages.common.login).toBe('Đăng nhập');
expect(viMessages.common.register).toBe('Đăng ký');
expect(viMessages.common.logout).toBe('Đăng xuất');
});
it('has nav namespace with Vietnamese navigation labels', () => {
expect(viMessages.nav.home).toBe('Trang chủ');
expect(viMessages.nav.search).toBe('Tìm kiếm');
expect(viMessages.nav.pricing).toBe('Bảng giá');
});
it('has propertyTypes namespace with all 9 property types in Vietnamese', () => {
const types = viMessages.propertyTypes;
expect(Object.keys(types)).toHaveLength(9);
expect(types.APARTMENT).toBe('Căn hộ');
expect(types.HOUSE).toBe('Nhà riêng');
expect(types.VILLA).toBe('Biệt thự');
expect(types.LAND).toBe('Đất nền');
expect(types.OFFICE).toBe('Văn phòng');
expect(types.SHOPHOUSE).toBe('Shophouse');
expect(types.ROOM_RENTAL).toBe('Phòng trọ');
expect(types.CONDOTEL).toBe('Condotel');
expect(types.SERVICED_APARTMENT).toBe('Căn hộ dịch vụ');
});
it('has transactionTypes in Vietnamese', () => {
expect(viMessages.transactionTypes.SALE).toBe('Bán');
expect(viMessages.transactionTypes.RENT).toBe('Cho thuê');
});
it('has language labels including self-referencing Vietnamese label', () => {
expect(viMessages.language.label).toBe('Ngôn ngữ');
expect(viMessages.language.vi).toBe('Tiếng Việt');
expect(viMessages.language.en).toBe('English');
});
it('has auth namespace with login form labels', () => {
expect(viMessages.auth.loginTitle).toBe('Đăng nhập');
expect(viMessages.auth.phone).toBe('Số điện thoại');
expect(viMessages.auth.password).toBe('Mật khẩu');
});
it('has pricing namespace with plan tier labels', () => {
expect(viMessages.pricing.tiers.FREE).toBe('Miễn phí');
expect(viMessages.pricing.tiers.AGENT_PRO).toBe('Môi giới Pro');
expect(viMessages.pricing.tiers.INVESTOR).toBe('Nhà đầu tư');
expect(viMessages.pricing.tiers.ENTERPRISE).toBe('Doanh nghiệp');
});
it('has search namespace with filter labels', () => {
expect(viMessages.search.filters).toBe('Bộ lọc');
expect(viMessages.search.district).toBe('Quận/huyện');
expect(viMessages.search.ward).toBe('Phường/xã');
});
});
describe('English (en.json) content', () => {
it('has a metadata namespace with English title', () => {
expect(enMessages.metadata.title).toBe(
'GoodGo — Vietnam Real Estate Platform',
);
});
it('has common namespace with English translations', () => {
expect(enMessages.common.loading).toBe('Loading...');
expect(enMessages.common.login).toBe('Login');
expect(enMessages.common.register).toBe('Register');
expect(enMessages.common.logout).toBe('Logout');
});
it('has propertyTypes in English', () => {
expect(enMessages.propertyTypes.APARTMENT).toBe('Apartment');
expect(enMessages.propertyTypes.HOUSE).toBe('House');
expect(enMessages.propertyTypes.VILLA).toBe('Villa');
});
it('has transactionTypes in English', () => {
expect(enMessages.transactionTypes.SALE).toBe('Sale');
expect(enMessages.transactionTypes.RENT).toBe('Rent');
});
});
describe('interpolation placeholders', () => {
it('vi.json common.errorCode contains {code} placeholder', () => {
expect(viMessages.common.errorCode).toContain('{code}');
});
it('en.json common.errorCode contains {code} placeholder', () => {
expect(enMessages.common.errorCode).toContain('{code}');
});
it('vi.json common.retriedCount contains {count} placeholder', () => {
expect(viMessages.common.retriedCount).toContain('{count}');
});
it('en.json common.retriedCount contains {count} placeholder', () => {
expect(enMessages.common.retriedCount).toContain('{count}');
});
it('vi.json search.bedroomsCount contains {count} placeholder', () => {
expect(viMessages.search.bedroomsCount).toContain('{count}');
});
it('en.json search.bedroomsCount contains {count} placeholder', () => {
expect(enMessages.search.bedroomsCount).toContain('{count}');
});
it('vi.json and en.json pricing.currentPlanBadge both contain {plan}', () => {
expect(viMessages.pricing.currentPlanBadge).toContain('{plan}');
expect(enMessages.pricing.currentPlanBadge).toContain('{plan}');
});
});
describe('no empty values', () => {
function findEmptyValues(
obj: Record<string, unknown>,
prefix = '',
): string[] {
const empty: string[] = [];
for (const key of Object.keys(obj)) {
const fullKey = prefix ? `${prefix}.${key}` : key;
const value = obj[key];
if (
typeof value === 'object' &&
value !== null &&
!Array.isArray(value)
) {
empty.push(
...findEmptyValues(value as Record<string, unknown>, fullKey),
);
} else if (typeof value === 'string' && value.trim() === '') {
empty.push(fullKey);
}
}
return empty;
}
it('vi.json has no empty string values', () => {
const emptyKeys = findEmptyValues(
viMessages as unknown as Record<string, unknown>,
);
expect(emptyKeys).toEqual([]);
});
it('en.json has no empty string values', () => {
const emptyKeys = findEmptyValues(
enMessages as unknown as Record<string, unknown>,
);
expect(emptyKeys).toEqual([]);
});
});
});