@meilisearch/instant-meilisearch
Version:
The search client to use Meilisearch with InstantSearch.
452 lines (381 loc) • 13.7 kB
text/typescript
import { describe, test, expect } from 'vitest'
import { MatchingStrategies } from 'meilisearch'
import { adaptSearchParams } from '../search-params-adapter.js'
import type {
OverridableMeiliSearchSearchParameters,
SearchContext,
} from '../../../types/index.js'
const DEFAULT_CONTEXT: SearchContext = {
indexUid: 'test',
pagination: { page: 0, hitsPerPage: 6, finite: false },
placeholderSearch: true,
keepZeroFacets: false,
attributesToHighlight: ['*'],
highlightPreTag: '<mark>',
highlightPostTag: '</mark>',
}
describe('Parameters adapter', () => {
test('adapting a basic searchContext ', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
})
expect(searchParams.attributesToHighlight).toContain('*')
expect(searchParams.attributesToHighlight?.length).toBe(1)
})
test('adapting a searchContext with filters and sort', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
facetFilters: [['genres:Drama', 'genres:Thriller'], ['title:Ariel']],
sort: 'id < 1',
})
expect(searchParams.filter).toStrictEqual([
['"genres"="Drama"', '"genres"="Thriller"'],
['"title"="Ariel"'],
])
expect(searchParams.sort).toStrictEqual(['id < 1'])
expect(searchParams.attributesToHighlight).toContain('*')
expect(searchParams.attributesToHighlight?.length).toBe(1)
})
test('adapting multi-word filters', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
facetFilters: [['My Genre:Science Fiction']],
})
expect(searchParams.filter).toStrictEqual([
['"My Genre"="Science Fiction"'],
])
})
test('adapting a searchContext with overridden Meilisearch parameters', () => {
const meiliSearchParams: OverridableMeiliSearchSearchParameters = {
attributesToHighlight: ['movies', 'genres'],
highlightPreTag: '<em>',
highlightPostTag: '</em>',
matchingStrategy: MatchingStrategies.ALL,
}
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
meiliSearchParams,
})
expect(searchParams.attributesToHighlight).toEqual(
meiliSearchParams.attributesToHighlight
)
expect(searchParams.highlightPreTag).toEqual(
meiliSearchParams.highlightPreTag
)
expect(searchParams.highlightPostTag).toEqual(
meiliSearchParams.highlightPostTag
)
expect(searchParams.matchingStrategy).toEqual(
meiliSearchParams.matchingStrategy
)
})
test('adapting a searchContext with overridden Meilisearch parameters for a specific index', () => {
const meiliSearchParams: OverridableMeiliSearchSearchParameters = {
attributesToHighlight: ['movies', 'genres'],
highlightPreTag: '<em>',
highlightPostTag: '</em>',
matchingStrategy: MatchingStrategies.ALL,
indexesOverrides: {
test: {
attributesToHighlight: ['release_date'],
highlightPreTag: '<span>',
highlightPostTag: '</span>',
matchingStrategy: MatchingStrategies.LAST,
},
},
}
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
meiliSearchParams,
})
expect(searchParams.attributesToHighlight).toEqual(
meiliSearchParams.indexesOverrides?.test?.attributesToHighlight
)
expect(searchParams.highlightPreTag).toEqual(
meiliSearchParams.indexesOverrides?.test?.highlightPreTag
)
expect(searchParams.highlightPostTag).toEqual(
meiliSearchParams.indexesOverrides?.test?.highlightPostTag
)
expect(searchParams.matchingStrategy).toEqual(
meiliSearchParams.indexesOverrides?.test?.matchingStrategy
)
})
test('hybrid search configuration can be set via search parameters', () => {
const hybridSearchConfig = {
semanticRatio: 0,
embedder: 'default',
}
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
meiliSearchParams: {
hybrid: hybridSearchConfig,
},
})
expect(searchParams.hybrid).toBe(hybridSearchConfig)
})
test('hybrid search configuration can be set via search parameters for a specific index', () => {
const hybridSearchConfig = {
semanticRatio: 0,
embedder: 'default',
}
const specificHybridSearchConfig = {
semanticRatio: 10,
embedder: 'default',
}
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
meiliSearchParams: {
hybrid: hybridSearchConfig,
indexesOverrides: {
test: { hybrid: specificHybridSearchConfig },
},
},
})
expect(searchParams.hybrid).toBe(specificHybridSearchConfig)
})
test('vector can be set via search parameters', () => {
const vector = [0, 1, 2]
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
meiliSearchParams: {
vector,
},
})
expect(searchParams.vector).toBe(vector)
})
test('vector can be set via search parameters for a specific index', () => {
const vector = [0, 1, 2]
const indexVector = [3, 4, 5]
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
meiliSearchParams: {
vector,
indexesOverrides: { test: { vector: indexVector } },
},
})
expect(searchParams.vector).toBe(indexVector)
})
test('ranking score threshold can be set via search parameters', () => {
const rankingScoreThreshold = 0.974
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
meiliSearchParams: {
rankingScoreThreshold,
},
})
expect(searchParams.rankingScoreThreshold).toBe(rankingScoreThreshold)
})
test('ranking score threshold can be set via search parameters for a specific index', () => {
const rankingScoreThreshold = 0.974
const indexRankingScoreThreshold = 0.567
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
meiliSearchParams: {
rankingScoreThreshold,
indexesOverrides: {
test: { rankingScoreThreshold: indexRankingScoreThreshold },
},
},
})
expect(searchParams.rankingScoreThreshold).toBe(indexRankingScoreThreshold)
})
test('distinct search configuration can be set via search parameters', () => {
const distinctSearchConfig = 'title'
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
meiliSearchParams: {
distinct: distinctSearchConfig,
},
})
expect(searchParams.distinct).toBe(distinctSearchConfig)
})
test('distinct search configuration can be set via search parameters for a specific', () => {
const distinctSearchConfig = 'title'
const indexDistinctSearchConfig = 'name'
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
meiliSearchParams: {
distinct: distinctSearchConfig,
indexesOverrides: {
test: { distinct: indexDistinctSearchConfig },
},
},
})
expect(searchParams.distinct).toBe(indexDistinctSearchConfig)
})
test('filter can be set via global override when IS filters are empty', () => {
const globalFilter = 'category = "books"'
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
// No facetFilters, filters, or numericFilters - empty IS filters
meiliSearchParams: {
filter: globalFilter,
},
})
expect(searchParams.filter).toBe(globalFilter)
})
test('filter can be set via per-index override when IS filters are empty', () => {
const globalFilter = 'category = "books"'
const indexFilter = 'status = "active"'
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
// No facetFilters, filters, or numericFilters - empty IS filters
meiliSearchParams: {
filter: globalFilter,
indexesOverrides: {
test: { filter: indexFilter },
},
},
})
// Per-index override should take precedence over global override
expect(searchParams.filter).toBe(indexFilter)
})
})
describe('Geo filter adapter', () => {
test('adapting a searchContext with filters, sort and geo filters ', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
facetFilters: [['genres:Drama', 'genres:Thriller'], ['title:Ariel']],
insideBoundingBox: '0,0,0,0',
sort: 'id < 1',
})
expect(searchParams.filter).toStrictEqual([
'_geoBoundingBox([0, 0], [0, 0])',
['"genres"="Drama"', '"genres"="Thriller"'],
['"title"="Ariel"'],
])
expect(searchParams.sort).toStrictEqual(['id < 1'])
expect(searchParams.attributesToHighlight).toContain('*')
expect(searchParams.attributesToHighlight?.length).toBe(1)
})
test('adapting a searchContext with only facetFilters and geo filters ', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
facetFilters: [['genres:Drama', 'genres:Thriller'], ['title:Ariel']],
insideBoundingBox: '0,0,0,0',
})
expect(searchParams.filter).toEqual([
'_geoBoundingBox([0, 0], [0, 0])',
['"genres"="Drama"', '"genres"="Thriller"'],
['"title"="Ariel"'],
])
expect(searchParams.attributesToHighlight).toContain('*')
expect(searchParams.attributesToHighlight?.length).toBe(1)
})
test('adapting a searchContext with only sort and geo filters ', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
insideBoundingBox: '0,0,0,0',
sort: 'id < 1',
})
expect(searchParams.filter).toEqual(['_geoBoundingBox([0, 0], [0, 0])'])
expect(searchParams.sort).toStrictEqual(['id < 1'])
expect(searchParams.attributesToHighlight).toContain('*')
expect(searchParams.attributesToHighlight?.length).toBe(1)
})
test('adapting a searchContext with no sort and no filters and geo filters ', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
insideBoundingBox: '0,0,0,0',
})
expect(searchParams.filter).toEqual(['_geoBoundingBox([0, 0], [0, 0])'])
expect(searchParams.attributesToHighlight).toContain('*')
expect(searchParams.attributesToHighlight?.length).toBe(1)
})
})
describe('Pagination adapter', () => {
test('adapting a searchContext with finite pagination', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
pagination: { page: 0, hitsPerPage: 6, finite: true },
})
expect(searchParams.page).toBe(1)
expect(searchParams.hitsPerPage).toBe(6)
})
test('adapting a searchContext with finite pagination on a later page', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
pagination: { page: 10, hitsPerPage: 6, finite: true },
})
expect(searchParams.page).toBe(11)
expect(searchParams.hitsPerPage).toBe(6)
})
test('adapting a searchContext with no finite pagination on page 1', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
})
expect(searchParams.limit).toBe(7)
expect(searchParams.offset).toBe(0)
})
test('adapting a searchContext with no finite pagination on page 2', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
pagination: { page: 1, hitsPerPage: 6, finite: false },
})
expect(searchParams.limit).toBe(7)
expect(searchParams.offset).toBe(6)
})
test('adapting a finite pagination with no placeholderSearch', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
query: '',
pagination: { page: 4, hitsPerPage: 6, finite: true },
placeholderSearch: false,
})
expect(searchParams.page).toBe(5)
expect(searchParams.hitsPerPage).toBe(0)
})
test('adapting a finite pagination with no placeholderSearch and a query', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
query: 'a',
pagination: { page: 4, hitsPerPage: 6, finite: true },
placeholderSearch: false,
})
expect(searchParams.page).toBe(5)
expect(searchParams.hitsPerPage).toBeGreaterThan(0)
})
test('adapting a finite pagination with no placeholderSearch and a facetFilter', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
query: '',
pagination: { page: 4, hitsPerPage: 6, finite: true },
placeholderSearch: false,
facetFilters: ['genres:Action'],
})
expect(searchParams.page).toBe(5)
expect(searchParams.hitsPerPage).toBeGreaterThan(0)
})
test('adapting a scroll pagination with no placeholderSearch', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
query: '',
pagination: { page: 4, hitsPerPage: 6, finite: false },
placeholderSearch: false,
})
expect(searchParams.limit).toBe(0)
expect(searchParams.offset).toBe(0)
})
test('adapting a scroll pagination with no placeholderSearch and a query', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
query: 'a',
pagination: { page: 4, hitsPerPage: 6, finite: false },
placeholderSearch: false,
})
expect(searchParams.limit).toBeGreaterThan(0)
expect(searchParams.offset).toBeGreaterThan(0)
})
test('adapting a scroll pagination with no placeholderSearch and a facetFilter', () => {
const searchParams = adaptSearchParams({
...DEFAULT_CONTEXT,
query: 'a',
pagination: { page: 4, hitsPerPage: 6, finite: false },
placeholderSearch: false,
facetFilters: ['genres:Action'],
})
expect(searchParams.limit).toBeGreaterThan(0)
expect(searchParams.offset).toBeGreaterThan(0)
})
})