feat(admin-ui): catalog list view per design handoff (D-list-view)

This commit is contained in:
Александр Зимин
2026-05-10 18:05:21 +00:00
parent 71b20c6a09
commit 75e844eb98
3 changed files with 247 additions and 282 deletions
@@ -1,13 +1,16 @@
/**
* Unit tests для catalog v2 pure helpers (`parseScopeFilter` + `sortDicts`).
* Component-level interactions tested через RTL отдельным test file (followup).
* Unit tests для catalog list-view pure helpers (parseScopeFilter +
* matchesQuery + groupByBundle).
*
* См. design_handoff_dictionary_catalog/review.md "Test plan" для полного
* matrix'а сценариев.
* См. design_handoff_dictionary_catalog/ для полной spec'и (List view D).
*/
import { describe, it, expect } from 'vitest'
import type { DictionaryDefinition } from '@/api/client'
import { parseScopeFilter, sortDicts, matchesQuery } from './dictionaries.index'
import {
parseScopeFilter,
matchesQuery,
groupByBundle,
} from './dictionaries.index'
const mkDict = (overrides: Partial<DictionaryDefinition>): DictionaryDefinition => ({
id: 'id-' + (overrides.name ?? 'x'),
@@ -67,54 +70,6 @@ describe('parseScopeFilter', () => {
})
})
describe('sortDicts', () => {
const a = mkDict({ name: 'alpha', displayName: 'Альфа', recordCount: 5, updatedAt: '2026-01-01T00:00:00Z' })
const b = mkDict({ name: 'beta', displayName: 'Бета', recordCount: 100, updatedAt: '2026-03-01T00:00:00Z' })
const c = mkDict({ name: 'gamma', displayName: 'Гамма', recordCount: 0, updatedAt: '2026-02-01T00:00:00Z' })
it('sort=name — alphabetical (RU localeCompare)', () => {
const out = sortDicts([c, a, b], 'name')
expect(out.map((d) => d.name)).toEqual(['alpha', 'beta', 'gamma'])
})
it('sort=name — uses displayName if present, fallback name', () => {
const noDisplay = mkDict({ name: 'zebra' }) // displayName=undefined → name used
const out = sortDicts([noDisplay, a, b], 'name')
// Default Intl collation сортирует latin до cyrillic — `zebra` идёт перед
// `Альфа`/`Бета`. Эта зависимость от Node-version'а / Intl backend'а.
// Проверяем стабильность сорт'а — порядок detereministic, не конкретный.
const sortedNames = out.map((d) => d.name)
expect(sortedNames).toContain('zebra')
expect(sortedNames).toContain('alpha')
expect(sortedNames).toContain('beta')
// displayName присутствует на a/b — sorted relatively к друг другу
const idxAlpha = sortedNames.indexOf('alpha')
const idxBeta = sortedNames.indexOf('beta')
expect(idxAlpha).toBeLessThan(idxBeta) // Альфа < Бета (RU collation)
})
it('sort=recordCount — descending, missing = 0', () => {
const noCount = mkDict({ name: 'omega', recordCount: undefined })
const out = sortDicts([a, noCount, b, c], 'recordCount')
expect(out.map((d) => d.recordCount ?? 0)).toEqual([100, 5, 0, 0])
})
it('sort=updated — descending по ISO timestamp', () => {
const out = sortDicts([a, b, c], 'updated')
expect(out.map((d) => d.updatedAt)).toEqual([
'2026-03-01T00:00:00Z',
'2026-02-01T00:00:00Z',
'2026-01-01T00:00:00Z',
])
})
it('does not mutate input array', () => {
const input = [c, a, b]
sortDicts(input, 'name')
expect(input.map((d) => d.name)).toEqual(['gamma', 'alpha', 'beta'])
})
})
describe('matchesQuery', () => {
const dict = mkDict({
name: 'spacecraft',
@@ -142,3 +97,37 @@ describe('matchesQuery', () => {
expect(matchesQuery(dict, 'абракадабра')).toBe(false)
})
})
describe('groupByBundle', () => {
it('groups dicts by bundle', () => {
const list = [
mkDict({ name: 'a', bundle: 'cuod' }),
mkDict({ name: 'b', bundle: 'geo' }),
mkDict({ name: 'c', bundle: 'cuod' }),
]
const out = groupByBundle(list)
expect(out.size).toBe(2)
expect(out.get('cuod')).toHaveLength(2)
expect(out.get('geo')).toHaveLength(1)
})
it('sorts dicts within group by displayName/name', () => {
const list = [
mkDict({ name: 'zebra', displayName: 'Зебра', bundle: 'cuod' }),
mkDict({ name: 'alpha', displayName: 'Альфа', bundle: 'cuod' }),
mkDict({ name: 'beta', displayName: 'Бета', bundle: 'cuod' }),
]
const out = groupByBundle(list)
expect(out.get('cuod')!.map((d) => d.name)).toEqual(['alpha', 'beta', 'zebra'])
})
it('empty input returns empty Map', () => {
expect(groupByBundle([]).size).toBe(0)
})
it('falls back to "default" bundle если bundle пуст', () => {
const list = [mkDict({ name: 'x', bundle: '' })]
const out = groupByBundle(list)
expect(out.has('default')).toBe(true)
})
})