/** * Unit tests для catalog list-view pure helpers (parseScopeFilter + * matchesQuery + groupByBundle). * * См. design_handoff_dictionary_catalog/ для полной spec'и (List view D). */ import { describe, it, expect } from 'vitest' import type { DictionaryDefinition } from '@/api/client' import { parseScopeFilter, matchesQuery, groupByBundle, uniqueRefBy, } from './dictionaries.index' import type { SchemaDependent } from '@/api/client' const mkDict = (overrides: Partial): DictionaryDefinition => ({ id: 'id-' + (overrides.name ?? 'x'), name: overrides.name ?? 'test', displayName: overrides.displayName, description: overrides.description, scope: overrides.scope ?? 'PUBLIC', schemaVersion: overrides.schemaVersion ?? '1.0.0', bundle: overrides.bundle ?? 'cuod', supportedLocales: overrides.supportedLocales ?? ['ru-RU'], defaultLocale: overrides.defaultLocale ?? 'ru-RU', redisProjectionEnabled: overrides.redisProjectionEnabled ?? false, approvalRequired: overrides.approvalRequired ?? false, approvalMinRole: overrides.approvalMinRole ?? null, recordCount: overrides.recordCount, createdAt: overrides.createdAt ?? '2026-01-01T00:00:00Z', updatedAt: overrides.updatedAt ?? '2026-01-01T00:00:00Z', }) describe('parseScopeFilter', () => { it('returns empty Set for undefined / empty input', () => { expect(parseScopeFilter(undefined).size).toBe(0) expect(parseScopeFilter('').size).toBe(0) }) it('parses single valid scope', () => { const out = parseScopeFilter('PUBLIC') expect(out.size).toBe(1) expect(out.has('PUBLIC')).toBe(true) }) it('parses CSV with multiple scopes', () => { const out = parseScopeFilter('PUBLIC,INTERNAL,RESTRICTED') expect(out.size).toBe(3) }) it('case-insensitive — lowercase + mixed case normalized to UPPER', () => { const out = parseScopeFilter('public,Internal') expect(out.has('PUBLIC')).toBe(true) expect(out.has('INTERNAL')).toBe(true) }) it('skips invalid scopes (bookmark protection)', () => { const out = parseScopeFilter('FOO,PUBLIC,BAR') expect(out.size).toBe(1) expect(out.has('PUBLIC')).toBe(true) }) it('deduplicates repeated scopes via Set', () => { const out = parseScopeFilter('PUBLIC,PUBLIC,public') expect(out.size).toBe(1) }) it('handles whitespace', () => { const out = parseScopeFilter(' PUBLIC , INTERNAL ') expect(out.size).toBe(2) }) }) describe('matchesQuery', () => { const dict = mkDict({ name: 'spacecraft', displayName: 'Космические аппараты', description: 'Реестр спутников', }) it('empty query matches anything', () => { expect(matchesQuery(dict, '')).toBe(true) }) it('matches name substring', () => { expect(matchesQuery(dict, 'space')).toBe(true) }) it('matches displayName (RU)', () => { expect(matchesQuery(dict, 'космич')).toBe(true) }) it('matches description', () => { expect(matchesQuery(dict, 'спутник')).toBe(true) }) it('returns false on miss', () => { 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) }) }) describe('uniqueRefBy', () => { const mkDep = (sourceDict: string, sourceField = 'fk'): SchemaDependent => ({ sourceDict, sourceDisplayName: `${sourceDict}-display`, sourceField, targetField: 'id', onClose: 'BLOCK', activeRecordsInSourceDict: 0, }) it('empty input returns empty array', () => { expect(uniqueRefBy([])).toEqual([]) }) it('preserves single entry as-is', () => { const out = uniqueRefBy([mkDep('missions')]) expect(out).toHaveLength(1) expect(out[0].sourceDict).toBe('missions') }) it('dedups by sourceDict — multiple fields → один chip', () => { const out = uniqueRefBy([ mkDep('satellites', 'type_id'), mkDep('satellites', 'mission_id'), mkDep('missions', 'fk'), ]) expect(out.map((d) => d.sourceDict)).toEqual(['satellites', 'missions']) }) it('preserves first-occurrence displayName', () => { const out = uniqueRefBy([ { ...mkDep('x'), sourceDisplayName: 'First' }, { ...mkDep('x'), sourceDisplayName: 'Second' }, ]) expect(out).toHaveLength(1) expect(out[0].sourceDisplayName).toBe('First') }) })