Skip to content

Commit 8d9409b

Browse files
meili-bors[bot]StriftStrift
authored
Merge #1347
1347: Add highlight metadata to autocomplete client r=flevi29 a=Strift # Pull Request ## Related issue Fixes #1337 ## What does this PR do? This PR enriches the `_highlightResult` object with metadata: - `fullyHighlighted`: whether the highlighted text covers the entire field - `matchLevel`: degree of the match (none, partial, or full) - `matchedWords`: array of words matched While adding the necessary tests, I also improved the existing tests for `fetchMeilisearchResults.ts`. Co-authored-by: Strift <[email protected]> Co-authored-by: Strift <[email protected]>
2 parents f64b254 + 731254f commit 8d9409b

File tree

4 files changed

+312
-21
lines changed

4 files changed

+312
-21
lines changed

.changeset/silly-badgers-hug.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@meilisearch/autocomplete-client": minor
3+
---
4+
5+
Add highlight metadata

packages/autocomplete-client/__tests__/test.utils.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,59 @@ const meilisearchClient = new MeiliSearch({
1717
apiKey: 'masterKey',
1818
})
1919

20+
export const MOVIES = [
21+
{
22+
id: 2,
23+
title: 'Ariel',
24+
overview:
25+
"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...",
26+
genres: ['Drama', 'Crime', 'Comedy'],
27+
poster: 'https://image.tmdb.org/t/p/w500/ojDg0PGvs6R9xYFodRct2kdI6wC.jpg',
28+
release_date: 593395200,
29+
},
30+
{
31+
id: 5,
32+
title: 'Four Rooms',
33+
overview:
34+
"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.",
35+
genres: ['Crime', 'Comedy'],
36+
poster: 'https://image.tmdb.org/t/p/w500/75aHn1NOYXh4M7L5shoeQ6NGykP.jpg',
37+
release_date: 818467200,
38+
},
39+
{
40+
id: 6,
41+
title: 'Judgment Night',
42+
overview:
43+
'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',
44+
genres: ['Action', 'Thriller', 'Crime'],
45+
poster: 'https://image.tmdb.org/t/p/w500/rYFAvSPlQUCebayLcxyK79yvtvV.jpg',
46+
release_date: 750643200,
47+
},
48+
{
49+
id: 11,
50+
title: 'Star Wars',
51+
overview:
52+
'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.',
53+
genres: ['Adventure', 'Action', 'Science Fiction'],
54+
poster: 'https://image.tmdb.org/t/p/w500/6FfCtAuVAW8XJjZ7eWeLibRLWTw.jpg',
55+
release_date: 233366400,
56+
},
57+
{
58+
id: 30,
59+
title: 'Magnetic Rose',
60+
overview: '',
61+
genres: ['Animation', 'Science Fiction'],
62+
poster: 'https://image.tmdb.org/t/p/w500/gSuHDeWemA1menrwfMRChnSmMVN.jpg',
63+
release_date: 819676800,
64+
},
65+
{
66+
id: 24,
67+
title: 'Kill Bill: Vol. 1',
68+
overview: null,
69+
genres: ['Action', 'Crime'],
70+
poster: 'https://image.tmdb.org/t/p/w500/v7TaX8kXMXs5yFFGR41guUDNcnB.jpg',
71+
release_date: 1065744000,
72+
},
73+
]
74+
2075
export { HOST, API_KEY, searchClient, dataset, meilisearchClient }
Lines changed: 148 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,187 @@
11
import { fetchMeilisearchResults } from '../fetchMeilisearchResults'
22
import {
33
searchClient,
4-
dataset,
4+
MOVIES,
55
meilisearchClient,
66
} from '../../../__tests__/test.utils'
77

8+
type Movie = (typeof MOVIES)[number]
9+
10+
const INDEX_NAME = 'movies_fetch-meilisearch-results-test'
11+
const FIRST_ITEM_ID = MOVIES[0].id
12+
const SECOND_ITEM_ID = MOVIES[1].id
13+
814
beforeAll(async () => {
9-
await meilisearchClient.deleteIndex('testUid')
10-
const task = await meilisearchClient.index('testUid').addDocuments(dataset)
15+
await meilisearchClient.deleteIndex(INDEX_NAME)
16+
const task = await meilisearchClient.index(INDEX_NAME).addDocuments(MOVIES)
1117
await meilisearchClient.waitForTask(task.taskUid)
1218
})
1319

1420
afterAll(async () => {
15-
await meilisearchClient.deleteIndex('testUid')
21+
await meilisearchClient.deleteIndex(INDEX_NAME)
1622
})
1723

1824
describe('fetchMeilisearchResults', () => {
1925
test('with default options', async () => {
20-
const results = await fetchMeilisearchResults<(typeof dataset)[0]>({
26+
const results = await fetchMeilisearchResults<Movie>({
2127
searchClient,
2228
queries: [
2329
{
24-
indexName: 'testUid',
30+
indexName: INDEX_NAME,
2531
query: '',
2632
},
2733
],
2834
})
2935

30-
expect(results[0].hits[0].id).toEqual(1)
31-
expect(results[0].hits[1].id).toEqual(2)
36+
expect(results[0].hits[0].id).toEqual(FIRST_ITEM_ID)
37+
expect(results[0].hits[1].id).toEqual(SECOND_ITEM_ID)
3238
})
3339

34-
test('with custom search parameters', async () => {
40+
test('with custom pagination', async () => {
3541
const results = await fetchMeilisearchResults({
3642
searchClient,
3743
queries: [
3844
{
39-
indexName: 'testUid',
40-
query: 'Hit',
45+
indexName: INDEX_NAME,
46+
query: '',
4147
params: {
4248
hitsPerPage: 1,
43-
highlightPreTag: '<test>',
44-
highlightPostTag: '</test>',
45-
page: 1,
49+
page: 1, // pages start at 0
50+
},
51+
},
52+
],
53+
})
54+
55+
expect(results[0].hits[0].id).toEqual(SECOND_ITEM_ID)
56+
})
57+
58+
test('with custom highlight tags', async () => {
59+
const results = await fetchMeilisearchResults({
60+
searchClient,
61+
queries: [
62+
{
63+
indexName: INDEX_NAME,
64+
query: 'Ariel',
65+
params: {
66+
highlightPreTag: '<b>',
67+
highlightPostTag: '</b>',
68+
},
69+
},
70+
],
71+
})
72+
73+
expect(results[0].hits[0]._highlightResult?.title?.value).toEqual(
74+
'<b>Ariel</b>'
75+
)
76+
})
77+
78+
test('highlight results contain highlighting metadata', async () => {
79+
const results = await fetchMeilisearchResults({
80+
searchClient,
81+
queries: [
82+
{
83+
indexName: INDEX_NAME,
84+
query: 'Ariel',
85+
},
86+
],
87+
})
88+
89+
expect(results[0].hits[0]._highlightResult?.id?.fullyHighlighted).toEqual(
90+
false
91+
)
92+
expect(results[0].hits[0]._highlightResult?.id?.matchLevel).toEqual('none')
93+
expect(results[0].hits[0]._highlightResult?.id?.matchedWords).toEqual([])
94+
expect(results[0].hits[0]._highlightResult?.id?.value).toEqual(String(2))
95+
})
96+
97+
test('highlight results contain fully highlighted match', async () => {
98+
const pre = '<em>'
99+
const post = '</em>'
100+
const results = await fetchMeilisearchResults({
101+
searchClient,
102+
queries: [
103+
{
104+
indexName: INDEX_NAME,
105+
query: 'Ariel',
106+
params: {
107+
highlightPreTag: pre,
108+
highlightPostTag: post,
109+
},
110+
},
111+
],
112+
})
113+
114+
expect(results[0].hits[0]._highlightResult?.title).toEqual({
115+
value: `${pre}Ariel${post}`,
116+
fullyHighlighted: true,
117+
matchLevel: 'full',
118+
matchedWords: ['Ariel'],
119+
})
120+
})
121+
122+
test('highlight results contains full match but not fully highlighted', async () => {
123+
const pre = '<em>'
124+
const post = '</em>'
125+
const results = await fetchMeilisearchResults({
126+
searchClient,
127+
queries: [
128+
{
129+
indexName: INDEX_NAME,
130+
query: 'Star',
131+
params: {
132+
highlightPreTag: pre,
133+
highlightPostTag: post,
46134
},
47135
},
48136
],
49137
})
50138

51-
expect(results[0].hits[0].id).toEqual(2)
52-
expect(results[0].hits[0]._highlightResult).toEqual({
53-
id: { value: '2' },
54-
label: { value: '<test>Hit</test> 2' },
139+
expect(results[0].hits[0]._highlightResult?.title).toEqual({
140+
value: `${pre}Star${post} Wars`,
141+
fullyHighlighted: false,
142+
matchLevel: 'full',
143+
matchedWords: ['Star'],
144+
})
145+
})
146+
147+
test('highlight results contain partially highlighted match', async () => {
148+
const pre = '<em>'
149+
const post = '</em>'
150+
const movie = MOVIES[0]
151+
const results = await fetchMeilisearchResults({
152+
searchClient,
153+
queries: [
154+
{
155+
indexName: INDEX_NAME,
156+
query: 'Tasto', // missing 'i' from 'Taisto'
157+
params: {
158+
highlightPreTag: pre,
159+
highlightPostTag: post,
160+
},
161+
},
162+
],
163+
})
164+
165+
expect(results[0].hits[0]._highlightResult?.overview).toEqual({
166+
// The first word of the overview is highlighted
167+
value: `${pre}Taist${post}` + (movie.overview as string).slice(5),
168+
fullyHighlighted: false,
169+
matchLevel: 'partial',
170+
matchedWords: ['Taist'],
171+
})
172+
})
173+
174+
test('highlight results contain no match', async () => {
175+
const results = await fetchMeilisearchResults({
176+
searchClient,
177+
queries: [{ indexName: INDEX_NAME, query: '' }],
178+
})
179+
180+
expect(results[0].hits[0]._highlightResult?.title).toEqual({
181+
value: 'Ariel',
182+
fullyHighlighted: false,
183+
matchLevel: 'none',
184+
matchedWords: [],
55185
})
56186
})
57187
})

0 commit comments

Comments
 (0)