teqo-backup/tests/unit/speechOmnibox.unit.spec.ts
Francisco Solla 45eef25b1a feat(C192): busca semântica no acervo de falas (Closes #1147)
Modo "Por tema" no acervo de falas: expansão determinística do tema via
DeepSeek (generateObject, timeout, fallback null) alimenta o motor lexical
existente (searchText/keywords OR), mantendo facetas AND, paginação por fala,
o OR de cortes do C174 e o access control intacto. Resultado de tema mostra
selo e "Por que apareceu" com trecho real (frase contínua), sem score;
degradação honesta para termo exato com aviso e vazio honesto com saídas.

- URL: mode=tema (default omitido; canonicaliza sem q), seletor otimista
- ai/expandSpeechSearchTheme + lib/speechThemeTerms (sanitiza saída do modelo)
- view model com themeMatchTerm + espelho speechMatchesSearchTerm
- UI do card de tema (selos, proveniência, ações) e aviso degradado
- sem migration/Consent/exposição pública; gate de leitura inalterado
- testes unit/int/e2e; design hi-fi estendido e certificado pelo designer
2026-09-18 17:48:28 -03:00

138 lines
3.9 KiB
TypeScript

// @vitest-environment node
import { describe, expect, it } from 'vitest'
import { parseSpeechListParams, type SpeechListState } from '@/utilities/speech/speechListUrl'
import {
applySpeechOmniboxSuggestion,
applySpeechSearchMode,
buildSpeechOmniboxChips,
clearSpeechOmnibox,
removeSpeechOmniboxChip,
} from '@/utilities/speech/speechOmnibox'
const state = (params: Record<string, string | string[]>): SpeechListState =>
parseSpeechListParams(params)
describe('buildSpeechOmniboxChips', () => {
it('labels every active facet', () => {
const chips = buildSpeechOmniboxChips({
state: state({
q: 'Farmácia Popular',
year: '2026',
topic: ['saude'],
scope: ['bahia'],
phase: ['Breves Comunicações'],
municipality: '12',
duration: ['curta'],
}),
municipalityLabelsById: new Map([[12, 'Camaçari']]),
})
expect(chips.map((chip) => chip.label)).toEqual([
'Busca: Farmácia Popular',
'Ano: 2026',
'Tema: Saúde',
'Alcance: Bahia',
'Fase: Breves Comunicações',
'Município: Camaçari',
'Duração: Até 2 min',
])
})
})
describe('applySpeechOmniboxSuggestion', () => {
it('sets the search query and resets the page', () => {
const action = applySpeechOmniboxSuggestion({
state: state({ page: '4', topic: ['saude'] }),
suggestionId: 'q:SUS',
})
expect(action).toEqual({
kind: 'url',
state: { page: 1, q: 'SUS', topics: ['saude'] },
})
})
it('toggles a facet value on and off', () => {
const on = applySpeechOmniboxSuggestion({
state: state({ topic: ['saude'] }),
suggestionId: 'topic:educacao',
})
expect(on.state.topics).toEqual(['saude', 'educacao'])
const off = applySpeechOmniboxSuggestion({
state: state({ topic: ['saude', 'educacao'] }),
suggestionId: 'topic:saude',
})
expect(off.state.topics).toEqual(['educacao'])
})
it('toggles numeric facets', () => {
const action = applySpeechOmniboxSuggestion({
state: state({ municipality: '12' }),
suggestionId: 'municipality:12',
})
expect(action.state.municipalities).toBeUndefined()
const years = applySpeechOmniboxSuggestion({
state: state({}),
suggestionId: 'year:2026',
})
expect(years.state.years).toEqual([2026])
})
})
describe('removeSpeechOmniboxChip', () => {
it('removes a single facet value and clears the field when it was the last', () => {
const kept = removeSpeechOmniboxChip({
state: state({ topic: ['saude', 'educacao'] }),
chipId: 'topic:saude',
})
expect(kept.state.topics).toEqual(['educacao'])
const emptied = removeSpeechOmniboxChip({
state: state({ topic: ['saude'] }),
chipId: 'topic:saude',
})
expect(emptied.state.topics).toBeUndefined()
})
it('removes the search chip and the theme mode with it (C192)', () => {
const action = removeSpeechOmniboxChip({
state: state({ q: 'SUS', mode: 'tema' }),
chipId: 'q',
})
expect(action.state.q).toBeUndefined()
expect(action.state.mode).toBeUndefined()
})
})
describe('applySpeechSearchMode (C192)', () => {
it('sets tema only with a query and resets the page', () => {
expect(applySpeechSearchMode({ state: state({ q: 'SUS', page: '4' }), mode: 'tema' })).toEqual({
kind: 'url',
state: { page: 1, q: 'SUS', mode: 'tema' },
})
expect(applySpeechSearchMode({ state: state({ page: '4' }), mode: 'tema' }).state.mode).toBe(
undefined,
)
})
it('clears the mode back to the exact search', () => {
const action = applySpeechSearchMode({
state: state({ q: 'SUS', mode: 'tema' }),
mode: 'termo',
})
expect(action.state.mode).toBeUndefined()
})
})
describe('clearSpeechOmnibox', () => {
it('resets every filter', () => {
expect(clearSpeechOmnibox(state({ q: 'SUS', topic: ['saude'] }))).toEqual({
kind: 'clear',
state: { page: 1 },
})
})
})