diff --git a/packages/autocomplete-client/__tests__/test.utils.ts b/packages/autocomplete-client/__tests__/test.utils.ts index 0e89a762..0f02abbf 100644 --- a/packages/autocomplete-client/__tests__/test.utils.ts +++ b/packages/autocomplete-client/__tests__/test.utils.ts @@ -26,6 +26,10 @@ export const MOVIES = [ genres: ['Drama', 'Crime', 'Comedy'], poster: 'https://image.tmdb.org/t/p/w500/ojDg0PGvs6R9xYFodRct2kdI6wC.jpg', release_date: 593395200, + reviews: [ + { id: 1, author: 'John', content: 'one star' }, + { id: 2, author: 'Jane', content: 'two stars' }, + ], }, { id: 5, @@ -35,6 +39,10 @@ export const MOVIES = [ genres: ['Crime', 'Comedy'], poster: 'https://image.tmdb.org/t/p/w500/75aHn1NOYXh4M7L5shoeQ6NGykP.jpg', release_date: 818467200, + reviews: [ + { id: 3, author: 'John', content: 'three stars' }, + { id: 4, author: 'Jane', content: 'four stars' }, + ], }, { id: 6, @@ -44,6 +52,10 @@ export const MOVIES = [ genres: ['Action', 'Thriller', 'Crime'], poster: 'https://image.tmdb.org/t/p/w500/rYFAvSPlQUCebayLcxyK79yvtvV.jpg', release_date: 750643200, + reviews: [ + { id: 5, author: 'John', content: 'five stars' }, + { id: 6, author: 'Jane', content: 'six stars' }, + ], }, { id: 11, @@ -53,6 +65,10 @@ export const MOVIES = [ genres: ['Adventure', 'Action', 'Science Fiction'], poster: 'https://image.tmdb.org/t/p/w500/6FfCtAuVAW8XJjZ7eWeLibRLWTw.jpg', release_date: 233366400, + reviews: [ + { id: 7, author: 'John', content: 'seven stars' }, + { id: 8, author: 'Jane', content: 'eight stars' }, + ], }, { id: 30, @@ -61,6 +77,10 @@ export const MOVIES = [ genres: ['Animation', 'Science Fiction'], poster: 'https://image.tmdb.org/t/p/w500/gSuHDeWemA1menrwfMRChnSmMVN.jpg', release_date: 819676800, + reviews: [ + { id: 9, author: 'John', content: 'nine stars' }, + { id: 10, author: 'Jane', content: 'ten stars' }, + ], }, { id: 24, @@ -69,6 +89,10 @@ export const MOVIES = [ genres: ['Action', 'Crime'], poster: 'https://image.tmdb.org/t/p/w500/v7TaX8kXMXs5yFFGR41guUDNcnB.jpg', release_date: 1065744000, + reviews: [ + { id: 11, author: 'John', content: 'eleven stars' }, + { id: 12, author: 'Jane', content: 'twelve stars' }, + ], }, ] diff --git a/packages/autocomplete-client/src/search/__tests__/fetchMeilisearchResults.test.ts b/packages/autocomplete-client/src/search/__tests__/fetchMeilisearchResults.test.ts index 70c1a860..edd8fb18 100644 --- a/packages/autocomplete-client/src/search/__tests__/fetchMeilisearchResults.test.ts +++ b/packages/autocomplete-client/src/search/__tests__/fetchMeilisearchResults.test.ts @@ -184,4 +184,18 @@ describe('fetchMeilisearchResults', () => { matchedWords: [], }) }) + + test('highlight results skips attributes missing value key', async () => { + const results = await fetchMeilisearchResults({ + searchClient, + queries: [ + { + indexName: INDEX_NAME, + query: 'kill bill', + }, + ], + }) + + expect(results[0].hits[0]._highlightResult?.reviews).toEqual(undefined) + }) }) diff --git a/packages/autocomplete-client/src/search/fetchMeilisearchResults.ts b/packages/autocomplete-client/src/search/fetchMeilisearchResults.ts index 01132187..ad5a7808 100644 --- a/packages/autocomplete-client/src/search/fetchMeilisearchResults.ts +++ b/packages/autocomplete-client/src/search/fetchMeilisearchResults.ts @@ -64,22 +64,27 @@ export function fetchMeilisearchResults>({ ...hit, _highlightResult: ( Object.entries(hit?._highlightResult || {}) as Array< - | [keyof TRecord, { value: string }] - | [keyof TRecord, Array<{ value: string }>] // if the field is an array + [keyof TRecord, PossibleHighlightResult] > ).reduce((acc, [field, highlightResult]) => { - return { - ...acc, - // if the field is an array, highlightResult is an array of objects - [field]: mapOneOrMany(highlightResult, (highlightResult) => - calculateHighlightMetadata( + if (!isDefinedHighlightValue(highlightResult)) { + return acc + } + + // if the field is an array, highlightResult is an array of objects + acc[field] = mapOneOrMany( + highlightResult, + (highlightResult) => { + return calculateHighlightMetadata( query.query || '', query.params?.highlightPreTag || HIGHLIGHT_PRE_TAG, query.params?.highlightPostTag || HIGHLIGHT_POST_TAG, highlightResult.value ) - ), - } + } + ) + + return acc }, {} as HighlightResult), })), } @@ -144,3 +149,26 @@ function calculateHighlightMetadata( function mapOneOrMany(value: T | T[], mapFn: (value: T) => U): U | U[] { return Array.isArray(value) ? value.map(mapFn) : mapFn(value) } + +type DefinedHighlightResult = { value: string } | Array<{ value: string }> // if the field is an array + +/** + * Some fields may not return a value at all - nested arrays/objects for example + * + * Ideally server honours the `attributesToHighlight` param and only includes + * those attributes in the response rather than all attributes (highlighted or + * not) + */ +type UndefinedHighlightResult = { value?: never } | Array<{ value?: never }> + +type PossibleHighlightResult = DefinedHighlightResult | UndefinedHighlightResult + +function isDefinedHighlightValue( + input: PossibleHighlightResult +): input is DefinedHighlightResult { + if (Array.isArray(input)) { + return input.every((r) => typeof r.value === 'string') + } + + return typeof input.value === 'string' +}