175 lines
5.3 KiB
TypeScript
175 lines
5.3 KiB
TypeScript
/**
|
|
* 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>): 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')
|
|
})
|
|
})
|