Skip to content

Add highlight metadata to autocomplete client #1347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silly-badgers-hug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@meilisearch/autocomplete-client": minor
---

Add highlight metadata
55 changes: 55 additions & 0 deletions packages/autocomplete-client/__tests__/test.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,59 @@ const meilisearchClient = new MeiliSearch({
apiKey: 'masterKey',
})

export const MOVIES = [
{
id: 2,
title: 'Ariel',
overview:
"Taisto Kasurinen is a Finnish coal miner whose father has just committed suicide and who is framed for a crime he did not commit. In jail, he starts to dream about leaving the country and starting a new life. He escapes from prison but things don't go as planned...",
genres: ['Drama', 'Crime', 'Comedy'],
poster: 'https://image.tmdb.org/t/p/w500/ojDg0PGvs6R9xYFodRct2kdI6wC.jpg',
release_date: 593395200,
},
{
id: 5,
title: 'Four Rooms',
overview:
"It's Ted the Bellhop's first night on the job...and the hotel's very unusual guests are about to place him in some outrageous predicaments. It seems that this evening's room service is serving up one unbelievable happening after another.",
genres: ['Crime', 'Comedy'],
poster: 'https://image.tmdb.org/t/p/w500/75aHn1NOYXh4M7L5shoeQ6NGykP.jpg',
release_date: 818467200,
},
{
id: 6,
title: 'Judgment Night',
overview:
'While racing to a boxing match, Frank, Mike, John and Rey get more than they bargained for. A wrong turn lands them directly in the path of Fallon, a vicious, wise-cracking drug lord. After accidentally witnessing Fallon murder a disloyal henchman, the four become his unwilling prey in a savage game of cat & mouse as they are mercilessly stalked through the urban jungle in this taut suspense drama',
genres: ['Action', 'Thriller', 'Crime'],
poster: 'https://image.tmdb.org/t/p/w500/rYFAvSPlQUCebayLcxyK79yvtvV.jpg',
release_date: 750643200,
},
{
id: 11,
title: 'Star Wars',
overview:
'Princess Leia is captured and held hostage by the evil Imperial forces in their effort to take over the galactic Empire. Venturesome Luke Skywalker and dashing captain Han Solo team together with the loveable robot duo R2-D2 and C-3PO to rescue the beautiful princess and restore peace and justice in the Empire.',
genres: ['Adventure', 'Action', 'Science Fiction'],
poster: 'https://image.tmdb.org/t/p/w500/6FfCtAuVAW8XJjZ7eWeLibRLWTw.jpg',
release_date: 233366400,
},
{
id: 30,
title: 'Magnetic Rose',
overview: '',
genres: ['Animation', 'Science Fiction'],
poster: 'https://image.tmdb.org/t/p/w500/gSuHDeWemA1menrwfMRChnSmMVN.jpg',
release_date: 819676800,
},
{
id: 24,
title: 'Kill Bill: Vol. 1',
overview: null,
genres: ['Action', 'Crime'],
poster: 'https://image.tmdb.org/t/p/w500/v7TaX8kXMXs5yFFGR41guUDNcnB.jpg',
release_date: 1065744000,
},
]

export { HOST, API_KEY, searchClient, dataset, meilisearchClient }
Original file line number Diff line number Diff line change
@@ -1,57 +1,187 @@
import { fetchMeilisearchResults } from '../fetchMeilisearchResults'
import {
searchClient,
dataset,
MOVIES,
meilisearchClient,
} from '../../../__tests__/test.utils'

type Movie = (typeof MOVIES)[number]

const INDEX_NAME = 'movies_fetch-meilisearch-results-test'
const FIRST_ITEM_ID = MOVIES[0].id
const SECOND_ITEM_ID = MOVIES[1].id

beforeAll(async () => {
await meilisearchClient.deleteIndex('testUid')
const task = await meilisearchClient.index('testUid').addDocuments(dataset)
await meilisearchClient.deleteIndex(INDEX_NAME)
const task = await meilisearchClient.index(INDEX_NAME).addDocuments(MOVIES)
await meilisearchClient.waitForTask(task.taskUid)
})

afterAll(async () => {
await meilisearchClient.deleteIndex('testUid')
await meilisearchClient.deleteIndex(INDEX_NAME)
})

describe('fetchMeilisearchResults', () => {
test('with default options', async () => {
const results = await fetchMeilisearchResults<(typeof dataset)[0]>({
const results = await fetchMeilisearchResults<Movie>({
searchClient,
queries: [
{
indexName: 'testUid',
indexName: INDEX_NAME,
query: '',
},
],
})

expect(results[0].hits[0].id).toEqual(1)
expect(results[0].hits[1].id).toEqual(2)
expect(results[0].hits[0].id).toEqual(FIRST_ITEM_ID)
expect(results[0].hits[1].id).toEqual(SECOND_ITEM_ID)
})

test('with custom search parameters', async () => {
test('with custom pagination', async () => {
const results = await fetchMeilisearchResults({
searchClient,
queries: [
{
indexName: 'testUid',
query: 'Hit',
indexName: INDEX_NAME,
query: '',
params: {
hitsPerPage: 1,
highlightPreTag: '<test>',
highlightPostTag: '</test>',
page: 1,
page: 1, // pages start at 0
},
},
],
})

expect(results[0].hits[0].id).toEqual(SECOND_ITEM_ID)
})

test('with custom highlight tags', async () => {
const results = await fetchMeilisearchResults({
searchClient,
queries: [
{
indexName: INDEX_NAME,
query: 'Ariel',
params: {
highlightPreTag: '<b>',
highlightPostTag: '</b>',
},
},
],
})

expect(results[0].hits[0]._highlightResult?.title?.value).toEqual(
'<b>Ariel</b>'
)
})

test('highlight results contain highlighting metadata', async () => {
const results = await fetchMeilisearchResults({
searchClient,
queries: [
{
indexName: INDEX_NAME,
query: 'Ariel',
},
],
})

expect(results[0].hits[0]._highlightResult?.id?.fullyHighlighted).toEqual(
false
)
expect(results[0].hits[0]._highlightResult?.id?.matchLevel).toEqual('none')
expect(results[0].hits[0]._highlightResult?.id?.matchedWords).toEqual([])
expect(results[0].hits[0]._highlightResult?.id?.value).toEqual(String(2))
})

test('highlight results contain fully highlighted match', async () => {
const pre = '<em>'
const post = '</em>'
const results = await fetchMeilisearchResults({
searchClient,
queries: [
{
indexName: INDEX_NAME,
query: 'Ariel',
params: {
highlightPreTag: pre,
highlightPostTag: post,
},
},
],
})

expect(results[0].hits[0]._highlightResult?.title).toEqual({
value: `${pre}Ariel${post}`,
fullyHighlighted: true,
matchLevel: 'full',
matchedWords: ['Ariel'],
})
})

test('highlight results contains full match but not fully highlighted', async () => {
const pre = '<em>'
const post = '</em>'
const results = await fetchMeilisearchResults({
searchClient,
queries: [
{
indexName: INDEX_NAME,
query: 'Star',
params: {
highlightPreTag: pre,
highlightPostTag: post,
},
},
],
})

expect(results[0].hits[0].id).toEqual(2)
expect(results[0].hits[0]._highlightResult).toEqual({
id: { value: '2' },
label: { value: '<test>Hit</test> 2' },
expect(results[0].hits[0]._highlightResult?.title).toEqual({
value: `${pre}Star${post} Wars`,
fullyHighlighted: false,
matchLevel: 'full',
matchedWords: ['Star'],
})
})

test('highlight results contain partially highlighted match', async () => {
const pre = '<em>'
const post = '</em>'
const movie = MOVIES[0]
const results = await fetchMeilisearchResults({
searchClient,
queries: [
{
indexName: INDEX_NAME,
query: 'Tasto', // missing 'i' from 'Taisto'
params: {
highlightPreTag: pre,
highlightPostTag: post,
},
},
],
})

expect(results[0].hits[0]._highlightResult?.overview).toEqual({
// The first word of the overview is highlighted
value: `${pre}Taist${post}` + (movie.overview as string).slice(5),
fullyHighlighted: false,
matchLevel: 'partial',
matchedWords: ['Taist'],
})
})

test('highlight results contain no match', async () => {
const results = await fetchMeilisearchResults({
searchClient,
queries: [{ indexName: INDEX_NAME, query: '' }],
})

expect(results[0].hits[0]._highlightResult?.title).toEqual({
value: 'Ariel',
fullyHighlighted: false,
matchLevel: 'none',
matchedWords: [],
})
})
})
Loading
Loading