fix(dict): name column в records list — pick localized value, не [object Object]

Раньше для записей с x-localized name (КА, антенна, ground_station,
operator) колонка 'name / code' показывала literal '[object Object]'
потому что String({'ru-RU': 'Канопус', 'en-US': 'Kanopus'}) =
toString() стандартный → '[object Object]'.

Решение:
- Новый shared util lib/locales.ts:
  - pickLocalized(value, defaultLocale) — извлекает строку из i18n
    объекта или plain string. Fallback на first non-empty key
    если defaultLocale не найден.
  - recordDisplayName(data, defaultLocale) — name → code → '—' chain.
- Records list использует recordDisplayName с dict.defaultLocale
  (берётся из DictionaryDetail).
- SchemaDrivenForm дедуплицирован — был свой локальный pickLocalized,
  теперь импортирует из util'ы.

Tests: +13 (76 → 89), unit tests на pickLocalized + recordDisplayName
покрывают все edge cases (plain string, localized object, fallback
chain, null/undefined input).
This commit is contained in:
Zimin A.N.
2026-05-06 21:05:50 +03:00
parent 66e89ce707
commit 5e1b39db03
4 changed files with 124 additions and 17 deletions
+69
View File
@@ -0,0 +1,69 @@
import { describe, expect, test } from 'vitest'
import { pickLocalized, recordDisplayName } from './locales'
describe('pickLocalized', () => {
test('plain string возвращается as-is', () => {
expect(pickLocalized('hello', 'ru-RU')).toBe('hello')
})
test('пустая string → undefined (caller покажет placeholder)', () => {
expect(pickLocalized('', 'ru-RU')).toBeUndefined()
})
test('object с defaultLocale → значение defaultLocale', () => {
expect(
pickLocalized({ 'ru-RU': 'Канопус', 'en-US': 'Kanopus' }, 'ru-RU'),
).toBe('Канопус')
})
test('defaultLocale пустой → fallback на любой non-empty key', () => {
expect(pickLocalized({ 'ru-RU': '', 'en-US': 'Kanopus' }, 'ru-RU')).toBe(
'Kanopus',
)
})
test('defaultLocale отсутствует → fallback на первый non-empty key', () => {
expect(pickLocalized({ 'en-US': 'Kanopus' }, 'ru-RU')).toBe('Kanopus')
})
test('null / undefined / number → undefined', () => {
expect(pickLocalized(null, 'ru-RU')).toBeUndefined()
expect(pickLocalized(undefined, 'ru-RU')).toBeUndefined()
expect(pickLocalized(42, 'ru-RU')).toBeUndefined()
})
test('объект без string значений → undefined', () => {
expect(pickLocalized({ 'ru-RU': { nested: 'oops' } }, 'ru-RU')).toBeUndefined()
})
})
describe('recordDisplayName', () => {
test('localized name → строка локали', () => {
expect(
recordDisplayName({ name: { 'ru-RU': 'Канопус', 'en-US': 'Kanopus' } }, 'ru-RU'),
).toBe('Канопус')
})
test('plain name string', () => {
expect(recordDisplayName({ name: 'Plain' }, 'ru-RU')).toBe('Plain')
})
test('нет name → fallback на code', () => {
expect(recordDisplayName({ code: 'OPERATIONAL' }, 'ru-RU')).toBe('OPERATIONAL')
})
test('нет name+code → em-dash', () => {
expect(recordDisplayName({ other: 'x' }, 'ru-RU')).toBe('—')
})
test('undefined data → em-dash', () => {
expect(recordDisplayName(undefined, 'ru-RU')).toBe('—')
})
test('localized с empty default locale → fallback на code не происходит, берём другую локаль', () => {
// Если name есть в other locale — оно приоритетнее code (name всегда нагляднее).
expect(
recordDisplayName({ name: { 'en-US': 'Eng only' }, code: 'X' }, 'ru-RU'),
).toBe('Eng only')
})
})