mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
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
65 lines
2.6 KiB
TypeScript
65 lines
2.6 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
normalizeForSearch,
|
|
speechMatchesSearchQuery,
|
|
speechMatchesSearchTerm,
|
|
uniqueByNormalizedForm,
|
|
} from '@/lib/speechSearch'
|
|
|
|
describe('normalizeForSearch', () => {
|
|
it('strips accents, lowercases and collapses whitespace', () => {
|
|
expect(normalizeForSearch(' Saúde Pública ')).toBe('saude publica')
|
|
expect(normalizeForSearch('São Félix do Coribe')).toBe('sao felix do coribe')
|
|
expect(normalizeForSearch('')).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('uniqueByNormalizedForm', () => {
|
|
it('dedupes by normalized form keeping the first spelling', () => {
|
|
expect(uniqueByNormalizedForm(['Saúde', 'saude', 'SUS', ' sus '])).toEqual(['Saúde', 'SUS'])
|
|
})
|
|
|
|
it('drops empty values', () => {
|
|
expect(uniqueByNormalizedForm(['', ' ', 'Bahia'])).toEqual(['Bahia'])
|
|
})
|
|
})
|
|
|
|
describe('speechMatchesSearchQuery', () => {
|
|
it('matches the normalized search text accent- and case-insensitively', () => {
|
|
const speech = { searchText: 'a saude publica baiana', keywords: [] }
|
|
expect(speechMatchesSearchQuery(speech, 'SAÚDE Pública')).toBe(true)
|
|
expect(speechMatchesSearchQuery(speech, 'educação')).toBe(false)
|
|
})
|
|
|
|
it('matches an official keyword case-insensitively, accent still significant', () => {
|
|
const speech = { searchText: '', keywords: ['Farmácia Popular', 'SUS'] }
|
|
expect(speechMatchesSearchQuery(speech, 'farmácia popular')).toBe(true)
|
|
expect(speechMatchesSearchQuery(speech, 'FARMÁCIA')).toBe(true)
|
|
// the SQL is ILIKE `%q%`: a different accent would not match there either
|
|
expect(speechMatchesSearchQuery(speech, 'farmacia')).toBe(false)
|
|
})
|
|
|
|
it('treats an empty or missing query as a match', () => {
|
|
const speech = { searchText: null, keywords: null }
|
|
expect(speechMatchesSearchQuery(speech, undefined)).toBe(true)
|
|
expect(speechMatchesSearchQuery(speech, ' ')).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('speechMatchesSearchTerm (C192)', () => {
|
|
it('mirrors a single textual where branch', () => {
|
|
const speech = { searchText: 'a saude publica baiana', keywords: ['Farmácia Popular'] }
|
|
expect(speechMatchesSearchTerm(speech, 'SAÚDE Pública')).toBe(true)
|
|
expect(speechMatchesSearchTerm(speech, 'FARMÁCIA')).toBe(true)
|
|
expect(speechMatchesSearchTerm(speech, 'educação')).toBe(false)
|
|
})
|
|
|
|
it('never matches an empty term', () => {
|
|
const speech = { searchText: 'a saude publica', keywords: ['SUS'] }
|
|
expect(speechMatchesSearchTerm(speech, undefined)).toBe(false)
|
|
expect(speechMatchesSearchTerm(speech, ' ')).toBe(false)
|
|
})
|
|
})
|