UNPKG

sanity-plugin-taxonomy-manager

Version:

Create and manage SKOS compliant taxonomies, thesauri, and classification schemes in Sanity Studio.

161 lines (141 loc) 5.28 kB
import {describe, expect, it} from 'vitest' import { assembleQueryText, recommendationsErrorMessage, recommendationsQuery, recommendedConceptIds, toConceptRecommendations, } from './semanticRecommendations' describe('recommendationsQuery', () => { const query = recommendationsQuery() it('matches the characterized GROQ output', () => { expect(query).toMatchSnapshot() }) it('scores skosConcepts by semanticSimilarity and returns the top $maxResults', () => { expect(query).toContain('_type == "skosConcept"') expect(query).toContain('score(text::semanticSimilarity($searchQuery))') expect(query).toContain('order(_score desc)') expect(query).toContain('[0...$maxResults]') expect(query).toContain('"conceptId": _id') expect(query).toContain('"score": _score') }) it("scopes candidates to the field's scheme via $schemeId membership", () => { expect(query).toContain('_type == "skosConceptScheme" && schemeId == $schemeId') expect(query).toContain('topConcepts[]._ref') expect(query).toContain('concepts[]._ref') expect(query).toContain('_id in') }) it('uses no part of the deprecated Embeddings Index API', () => { expect(query).not.toContain('text::embedding') expect(query).not.toContain('embeddings-index') }) }) describe('assembleQueryText', () => { it('joins the non-empty field values with a space, in order', () => { const text = assembleQueryText([ {name: 'title', value: 'Summer'}, {name: 'description', value: 'reading list'}, ]) expect(text).toBe('Summer reading list') }) it('throws naming the single empty field', () => { expect(() => assembleQueryText([ {name: 'title', value: 'Summer'}, {name: 'description', value: ''}, ]) ).toThrowError('Please fill out the description field to enable match scores.') }) it('throws listing every empty field when more than one is empty', () => { expect(() => assembleQueryText([ {name: 'title', value: ''}, {name: 'description', value: ' '}, ]) ).toThrowError( 'The following fields must be filled out to enable match scores: title, description' ) }) it('treats whitespace-only and non-string values as empty', () => { expect(() => assembleQueryText([ {name: 'title', value: ' '}, {name: 'count', value: 42}, ]) ).toThrowError( 'The following fields must be filled out to enable match scores: title, count' ) }) }) describe('toConceptRecommendations', () => { it('maps rows to {conceptId, score}', () => { const recs = toConceptRecommendations([ {conceptId: 'concept-a', score: 0.9}, {conceptId: 'concept-b', score: 0.4}, ]) expect(recs).toEqual([ {conceptId: 'concept-a', score: 0.9}, {conceptId: 'concept-b', score: 0.4}, ]) }) it('normalizes draft and version ids to their published form', () => { const recs = toConceptRecommendations([ {conceptId: 'drafts.concept-a', score: 0.9}, {conceptId: 'versions.rABC.concept-b', score: 0.4}, ]) expect(recs).toEqual([ {conceptId: 'concept-a', score: 0.9}, {conceptId: 'concept-b', score: 0.4}, ]) }) it('drops rows missing a string id or numeric score', () => { const recs = toConceptRecommendations([ {conceptId: 'concept-a', score: 0.9}, {conceptId: '', score: 0.5}, {conceptId: 'concept-c', score: null as unknown as number}, null as unknown as {conceptId: string; score: number}, ]) expect(recs).toEqual([{conceptId: 'concept-a', score: 0.9}]) }) it('returns an empty array for null or undefined input', () => { expect(toConceptRecommendations(null)).toEqual([]) expect(toConceptRecommendations(undefined)).toEqual([]) }) }) describe('recommendedConceptIds', () => { it('collects the recommended concept ids as a set', () => { const ids = recommendedConceptIds([ {conceptId: 'a', score: 0.9}, {conceptId: 'b', score: 0.4}, ]) expect(ids).toEqual(new Set(['a', 'b'])) }) it('de-duplicates repeated concept ids', () => { const ids = recommendedConceptIds([ {conceptId: 'a', score: 0.9}, {conceptId: 'a', score: 0.3}, {conceptId: 'b', score: 0.5}, ]) expect(ids).toEqual(new Set(['a', 'b'])) }) it('returns an empty set for no recommendations', () => { expect(recommendedConceptIds([]).size).toBe(0) }) }) describe('recommendationsErrorMessage', () => { it('flags an embeddings-related error as the dataset not being enabled', () => { const message = recommendationsErrorMessage( new Error('text::semanticSimilarity requires embeddings to be enabled') ) expect(message).toBe("Semantic recommendations aren't enabled for this dataset.") }) it('falls back to a generic message for any other failure', () => { expect(recommendationsErrorMessage(new Error('Network request failed'))).toBe( 'Unable to load semantic recommendations.' ) expect(recommendationsErrorMessage('socket hang up')).toBe( 'Unable to load semantic recommendations.' ) expect(recommendationsErrorMessage(undefined)).toBe('Unable to load semantic recommendations.') }) })