Skip to content

Commit 08219ef

Browse files
committed
Add tests for partial matches and full matches, non fully highlighted
1 parent a95a005 commit 08219ef

File tree

2 files changed

+92
-12
lines changed

2 files changed

+92
-12
lines changed

packages/autocomplete-client/src/search/__tests__/fetchMeilisearchResults.test.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe('fetchMeilisearchResults', () => {
9595
expect(results[0].hits[0]._highlightResult?.id?.value).toEqual(String(2))
9696
})
9797

98-
test('with fully highlighted match', async () => {
98+
test('highlight results contain fully highlighted match', async () => {
9999
const pre = '<em>'
100100
const post = '</em>'
101101
const results = await fetchMeilisearchResults({
@@ -119,4 +119,70 @@ describe('fetchMeilisearchResults', () => {
119119
matchedWords: ['Ariel'],
120120
})
121121
})
122+
123+
test('highlight results contains full match but not fully highlighted', async () => {
124+
const pre = '<em>'
125+
const post = '</em>'
126+
const results = await fetchMeilisearchResults({
127+
searchClient,
128+
queries: [
129+
{
130+
indexName: INDEX_NAME,
131+
query: 'Star',
132+
params: {
133+
highlightPreTag: pre,
134+
highlightPostTag: post,
135+
},
136+
},
137+
],
138+
})
139+
140+
expect(results[0].hits[0]._highlightResult?.title).toEqual({
141+
value: `${pre}Star${post} Wars`,
142+
fullyHighlighted: false,
143+
matchLevel: 'full',
144+
matchedWords: ['Star'],
145+
})
146+
})
147+
148+
test('highlight results contain partially highlighted match', async () => {
149+
const pre = '<em>'
150+
const post = '</em>'
151+
const movie = MOVIES[0]
152+
const results = await fetchMeilisearchResults({
153+
searchClient,
154+
queries: [
155+
{
156+
indexName: INDEX_NAME,
157+
query: 'Tasto', // missing 'i' from 'Taisto'
158+
params: {
159+
highlightPreTag: pre,
160+
highlightPostTag: post,
161+
},
162+
},
163+
],
164+
})
165+
166+
expect(results[0].hits[0]._highlightResult?.overview).toEqual({
167+
// The first word of the overview is highlighted
168+
value: `${pre}Taist${post}` + (movie.overview as string).slice(5),
169+
fullyHighlighted: false,
170+
matchLevel: 'partial',
171+
matchedWords: ['Taist'],
172+
})
173+
})
174+
175+
test('highlight results contain no match', async () => {
176+
const results = await fetchMeilisearchResults({
177+
searchClient,
178+
queries: [{ indexName: INDEX_NAME, query: '' }],
179+
})
180+
181+
expect(results[0].hits[0]._highlightResult?.title).toEqual({
182+
value: 'Ariel',
183+
fullyHighlighted: false,
184+
matchLevel: 'none',
185+
matchedWords: [],
186+
})
187+
})
122188
})

packages/autocomplete-client/src/search/fetchMeilisearchResults.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,21 @@ export function fetchMeilisearchResults<TRecord = Record<string, any>>({
7878
...acc,
7979
[field]: highlightResult.map((highlight) =>
8080
calculateHighlightMetadata(
81-
highlight.value,
81+
query.query || '',
8282
query.params?.highlightPreTag || HIGHLIGHT_PRE_TAG,
83-
query.params?.highlightPostTag || HIGHLIGHT_POST_TAG
83+
query.params?.highlightPostTag || HIGHLIGHT_POST_TAG,
84+
highlight.value
8485
)
8586
),
8687
}
8788
}
8889
return {
8990
...acc,
9091
[field]: calculateHighlightMetadata(
91-
highlightResult.value,
92+
query.query || '',
9293
query.params?.highlightPreTag || HIGHLIGHT_PRE_TAG,
93-
query.params?.highlightPostTag || HIGHLIGHT_POST_TAG
94+
query.params?.highlightPostTag || HIGHLIGHT_POST_TAG,
95+
highlightResult.value
9496
),
9597
}
9698
}, {} as HighlightResult<TRecord>),
@@ -102,21 +104,33 @@ export function fetchMeilisearchResults<TRecord = Record<string, any>>({
102104
)
103105
}
104106

107+
/**
108+
* Calculate the highlight metadata for a given highlight value.
109+
* @param query - The query string.
110+
* @param preTag - The pre tag.
111+
* @param postTag - The post tag.
112+
* @param highlightValue - The highlight value response from Meilisearch.
113+
* @returns The highlight metadata.
114+
*/
105115
function calculateHighlightMetadata(
106-
value: string,
116+
query: string,
107117
preTag: string,
108-
postTag: string
118+
postTag: string,
119+
highlightValue: string
109120
): HighlightMetadata {
110121
// Extract all highlighted segments
111122
const highlightRegex = new RegExp(`${preTag}(.*?)${postTag}`, 'g')
112123
const matches: string[] = []
113124
let match
114-
while ((match = highlightRegex.exec(value)) !== null) {
125+
while ((match = highlightRegex.exec(highlightValue)) !== null) {
115126
matches.push(match[1])
116127
}
117128

118-
// Remove highlight tags to get the original, unhighlighted text
119-
const cleanValue = value.replace(new RegExp(`${preTag}|${postTag}`, 'g'), '')
129+
// Remove highlight tags to get the highlighted text without the tags
130+
const cleanValue = highlightValue.replace(
131+
new RegExp(`${preTag}|${postTag}`, 'g'),
132+
''
133+
)
120134

121135
// Determine if the entire attribute is highlighted
122136
// fullyHighlighted = true if cleanValue and the concatenation of all matched segments are identical
@@ -129,11 +143,11 @@ function calculateHighlightMetadata(
129143
// - 'full' if all text is fully highlighted
130144
let matchLevel: 'none' | 'partial' | 'full' = 'none'
131145
if (matches.length > 0) {
132-
matchLevel = fullyHighlighted ? 'full' : 'partial'
146+
matchLevel = cleanValue.includes(query) ? 'full' : 'partial'
133147
}
134148

135149
return {
136-
value,
150+
value: highlightValue,
137151
fullyHighlighted,
138152
matchLevel,
139153
matchedWords: matches,

0 commit comments

Comments
 (0)