From 9f4b6bdc58d7e1d9b4d22692f4aeaf0b0309e008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 17 Sep 2020 16:46:35 +0200 Subject: [PATCH] feat(algolia): type highlight presets --- .../src/__tests__/autocomplete.test.ts | 47 +++++-- .../src/{constants.ts => constants/index.ts} | 0 .../src/highlight.ts | 131 ------------------ .../src/highlight/ParseAlgoliaHitParams.ts | 5 + .../src/highlight/ParsedAttribute.ts | 4 + .../__tests__/highlight.test.ts | 43 +++++- .../src/highlight/getAttributeValueByPath.ts | 15 ++ .../src/highlight/index.ts | 0 .../src/highlight/parseAlgoliaHitHighlight.ts | 22 +++ .../parseAlgoliaHitReverseHighlight.ts | 12 ++ .../parseAlgoliaHitReverseSnippet.ts | 12 ++ .../src/highlight/parseAlgoliaHitSnippet.ts | 22 +++ .../src/highlight/parseAttribute.ts | 59 ++++++++ .../src/highlight/reverseHighlightedParts.ts | 10 ++ .../autocomplete-preset-algolia/src/index.ts | 13 +- .../src/results.ts | 60 -------- .../__tests__/search.test.ts} | 91 +++++++++--- .../src/search/getAlgoliaHits.ts | 14 ++ .../src/search/getAlgoliaResults.ts | 12 ++ .../src/search/search.ts | 33 +++++ 20 files changed, 365 insertions(+), 240 deletions(-) rename packages/autocomplete-preset-algolia/src/{constants.ts => constants/index.ts} (100%) delete mode 100644 packages/autocomplete-preset-algolia/src/highlight.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/ParseAlgoliaHitParams.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/ParsedAttribute.ts rename packages/autocomplete-preset-algolia/src/{ => highlight}/__tests__/highlight.test.ts (87%) create mode 100644 packages/autocomplete-preset-algolia/src/highlight/getAttributeValueByPath.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/index.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitHighlight.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitReverseHighlight.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitReverseSnippet.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitSnippet.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/parseAttribute.ts create mode 100644 packages/autocomplete-preset-algolia/src/highlight/reverseHighlightedParts.ts delete mode 100644 packages/autocomplete-preset-algolia/src/results.ts rename packages/autocomplete-preset-algolia/src/{__tests__/results.test.ts => search/__tests__/search.test.ts} (65%) create mode 100644 packages/autocomplete-preset-algolia/src/search/getAlgoliaHits.ts create mode 100644 packages/autocomplete-preset-algolia/src/search/getAlgoliaResults.ts create mode 100644 packages/autocomplete-preset-algolia/src/search/search.ts diff --git a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts index 61a151ff4..ce75334d6 100644 --- a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts @@ -46,6 +46,19 @@ describe('autocomplete-js', () => {
+ -
diff --git a/packages/autocomplete-preset-algolia/src/constants.ts b/packages/autocomplete-preset-algolia/src/constants/index.ts similarity index 100% rename from packages/autocomplete-preset-algolia/src/constants.ts rename to packages/autocomplete-preset-algolia/src/constants/index.ts diff --git a/packages/autocomplete-preset-algolia/src/highlight.ts b/packages/autocomplete-preset-algolia/src/highlight.ts deleted file mode 100644 index 6b06cf983..000000000 --- a/packages/autocomplete-preset-algolia/src/highlight.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { HIGHLIGHT_PRE_TAG, HIGHLIGHT_POST_TAG } from './constants'; - -const htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', -}; - -type ParseAttributeParams = { - highlightedValue: string; - ignoreEscape?: string[]; -}; - -type ParsedAttribute = { value: string; isHighlighted: boolean }; - -export function parseAttribute({ - highlightedValue, - ignoreEscape = [], -}: ParseAttributeParams): ParsedAttribute[] { - const unescapedHtmlRegex = new RegExp( - `[${Object.keys(htmlEscapes) - .filter((character) => ignoreEscape.indexOf(character) === -1) - .join('')}]`, - 'g' - ); - const hasUnescapedHtmlRegex = RegExp(unescapedHtmlRegex.source); - - function escape(value: string) { - return hasUnescapedHtmlRegex.test(value) - ? value.replace(unescapedHtmlRegex, (key) => htmlEscapes[key]) - : value; - } - - const splitByPreTag = highlightedValue.split(HIGHLIGHT_PRE_TAG); - const firstValue = splitByPreTag.shift(); - const elements = !firstValue - ? [] - : [{ value: escape(firstValue), isHighlighted: false }]; - - splitByPreTag.forEach((split) => { - const splitByPostTag = split.split(HIGHLIGHT_POST_TAG); - - elements.push({ - value: escape(splitByPostTag[0]), - isHighlighted: true, - }); - - if (splitByPostTag[1] !== '') { - elements.push({ - value: escape(splitByPostTag[1]), - isHighlighted: false, - }); - } - }); - - return elements; -} - -function getAttributeValueByPath(hit: object, path: string): string { - const parts = path.split('.'); - const value = parts.reduce((current, key) => current && current[key], hit); - - if (typeof value !== 'string') { - throw new Error( - `The attribute ${JSON.stringify(path)} does not exist on the hit.` - ); - } - - return value; -} - -function reverseHighlightedParts(parts: ParsedAttribute[]) { - // We don't want to highlight the whole word when no parts match. - if (!parts.some((part) => part.isHighlighted)) { - return parts.map((part) => ({ ...part, isHighlighted: false })); - } - - return parts.map((part) => ({ ...part, isHighlighted: !part.isHighlighted })); -} - -type SharedParseAttributeParams = { - hit: TItem; - attribute: keyof TItem; - ignoreEscape?: string[]; -}; - -export function parseAlgoliaHitHighlight({ - hit, - attribute, - ignoreEscape, -}: SharedParseAttributeParams): ParsedAttribute[] { - const highlightedValue = getAttributeValueByPath( - hit, - `_highlightResult.${attribute}.value` - ); - - return parseAttribute({ - highlightedValue, - ignoreEscape, - }); -} - -export function parseAlgoliaHitReverseHighlight( - props: SharedParseAttributeParams -): ParsedAttribute[] { - return reverseHighlightedParts(parseAlgoliaHitHighlight(props)); -} - -export function parseAlgoliaHitSnippet({ - hit, - attribute, - ignoreEscape, -}: SharedParseAttributeParams): ParsedAttribute[] { - const highlightedValue = getAttributeValueByPath( - hit, - `_snippetResult.${attribute}.value` - ); - - return parseAttribute({ - highlightedValue, - ignoreEscape, - }); -} - -export function parseAlgoliaHitReverseSnippet( - props: SharedParseAttributeParams -): ParsedAttribute[] { - return reverseHighlightedParts(parseAlgoliaHitSnippet(props)); -} diff --git a/packages/autocomplete-preset-algolia/src/highlight/ParseAlgoliaHitParams.ts b/packages/autocomplete-preset-algolia/src/highlight/ParseAlgoliaHitParams.ts new file mode 100644 index 000000000..3c97d8b1a --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/ParseAlgoliaHitParams.ts @@ -0,0 +1,5 @@ +export type ParseAlgoliaHitParams = { + hit: TItem; + attribute: keyof TItem; + ignoreEscape?: string[]; +}; diff --git a/packages/autocomplete-preset-algolia/src/highlight/ParsedAttribute.ts b/packages/autocomplete-preset-algolia/src/highlight/ParsedAttribute.ts new file mode 100644 index 000000000..b5a0ea33b --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/ParsedAttribute.ts @@ -0,0 +1,4 @@ +export type ParsedAttribute = { + value: string; + isHighlighted: boolean; +}; diff --git a/packages/autocomplete-preset-algolia/src/__tests__/highlight.test.ts b/packages/autocomplete-preset-algolia/src/highlight/__tests__/highlight.test.ts similarity index 87% rename from packages/autocomplete-preset-algolia/src/__tests__/highlight.test.ts rename to packages/autocomplete-preset-algolia/src/highlight/__tests__/highlight.test.ts index bdf3a2fcf..1f13575d3 100644 --- a/packages/autocomplete-preset-algolia/src/__tests__/highlight.test.ts +++ b/packages/autocomplete-preset-algolia/src/highlight/__tests__/highlight.test.ts @@ -1,9 +1,7 @@ -import { - parseAlgoliaHitHighlight, - parseAlgoliaHitReverseHighlight, - parseAlgoliaHitSnippet, - parseAlgoliaHitReverseSnippet, -} from '../highlight'; +import { parseAlgoliaHitHighlight } from '../parseAlgoliaHitHighlight'; +import { parseAlgoliaHitReverseHighlight } from '../parseAlgoliaHitReverseHighlight'; +import { parseAlgoliaHitReverseSnippet } from '../parseAlgoliaHitReverseSnippet'; +import { parseAlgoliaHitSnippet } from '../parseAlgoliaHitSnippet'; describe('highlight', () => { describe('parseAlgoliaHitHighlight', () => { @@ -12,10 +10,15 @@ describe('highlight', () => { parseAlgoliaHitHighlight({ attribute: 'title', hit: { + objectID: '1', + title: 'Hello there', _highlightResult: { title: { value: '__aa-highlight__He__/aa-highlight__llo t__aa-highlight__he__/aa-highlight__re', + matchLevel: 'partial', + matchedWords: [], + fullyHighlighted: false, }, }, }, @@ -47,6 +50,8 @@ describe('highlight', () => { parseAlgoliaHitHighlight({ attribute: 'title', hit: { + objectID: '1', + title: 'Hello there', _highlightResult: { title: { value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, @@ -73,6 +78,8 @@ describe('highlight', () => { parseAlgoliaHitHighlight({ attribute: 'title', hit: { + objectID: '1', + title: 'Hello there', _highlightResult: { title: { value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, @@ -102,6 +109,8 @@ describe('highlight', () => { parseAlgoliaHitReverseHighlight({ attribute: 'title', hit: { + objectID: '1', + title: 'Hello there', _highlightResult: { title: { value: @@ -136,7 +145,11 @@ describe('highlight', () => { expect( parseAlgoliaHitReverseHighlight({ attribute: 'title', - hit: { _highlightResult: { title: { value: 'Hello' } } }, + hit: { + objectID: '1', + title: 'Hello there', + _highlightResult: { title: { value: 'Hello' } }, + }, }) ).toMatchInlineSnapshot(` Array [ @@ -153,6 +166,8 @@ describe('highlight', () => { parseAlgoliaHitReverseHighlight({ attribute: 'title', hit: { + objectID: '1', + title: 'Hello there', _highlightResult: { title: { value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, @@ -179,6 +194,8 @@ describe('highlight', () => { parseAlgoliaHitReverseHighlight({ attribute: 'title', hit: { + objectID: '1', + title: 'Hello there', _highlightResult: { title: { value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, @@ -208,6 +225,8 @@ describe('highlight', () => { parseAlgoliaHitReverseSnippet({ attribute: 'title', hit: { + objectID: '1', + title: 'Hello there', _snippetResult: { title: { value: @@ -243,6 +262,8 @@ describe('highlight', () => { parseAlgoliaHitReverseSnippet({ attribute: 'title', hit: { + objectID: '1', + title: 'Hello there', _snippetResult: { title: { value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, @@ -269,6 +290,8 @@ describe('highlight', () => { parseAlgoliaHitReverseSnippet({ attribute: 'title', hit: { + objectID: '1', + title: 'Hello there', _snippetResult: { title: { value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, @@ -298,6 +321,8 @@ describe('highlight', () => { parseAlgoliaHitSnippet({ attribute: 'title', hit: { + objectID: '1', + title: 'Hello there', _snippetResult: { title: { value: @@ -334,6 +359,8 @@ describe('highlight', () => { parseAlgoliaHitSnippet({ attribute: 'title', hit: { + objectID: '1', + title: 'Hello there', _snippetResult: { title: { value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, @@ -360,6 +387,8 @@ describe('highlight', () => { parseAlgoliaHitSnippet({ attribute: 'title', hit: { + objectID: '1', + title: 'Hello there', _snippetResult: { title: { value: `__aa-highlight__Food__/aa-highlight__ & 'n' "Music"`, diff --git a/packages/autocomplete-preset-algolia/src/highlight/getAttributeValueByPath.ts b/packages/autocomplete-preset-algolia/src/highlight/getAttributeValueByPath.ts new file mode 100644 index 000000000..1c7606466 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/getAttributeValueByPath.ts @@ -0,0 +1,15 @@ +export function getAttributeValueByPath(hit: THit, path: string): string { + const parts = path.split('.'); + const value = parts.reduce( + (current, key) => current && current[key], + hit as any + ); + + if (typeof value !== 'string') { + throw new Error( + `The attribute ${JSON.stringify(path)} does not exist on the hit.` + ); + } + + return value; +} diff --git a/packages/autocomplete-preset-algolia/src/highlight/index.ts b/packages/autocomplete-preset-algolia/src/highlight/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitHighlight.ts b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitHighlight.ts new file mode 100644 index 000000000..25091ba35 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitHighlight.ts @@ -0,0 +1,22 @@ +import { Hit } from '@algolia/client-search'; + +import { getAttributeValueByPath } from './getAttributeValueByPath'; +import { ParseAlgoliaHitParams } from './ParseAlgoliaHitParams'; +import { parseAttribute } from './parseAttribute'; +import { ParsedAttribute } from './ParsedAttribute'; + +export function parseAlgoliaHitHighlight>({ + hit, + attribute, + ignoreEscape, +}: ParseAlgoliaHitParams): ParsedAttribute[] { + const highlightedValue = getAttributeValueByPath( + hit, + `_highlightResult.${attribute}.value` + ); + + return parseAttribute({ + highlightedValue, + ignoreEscape, + }); +} diff --git a/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitReverseHighlight.ts b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitReverseHighlight.ts new file mode 100644 index 000000000..60fd7c6ec --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitReverseHighlight.ts @@ -0,0 +1,12 @@ +import { Hit } from '@algolia/client-search'; + +import { parseAlgoliaHitHighlight } from './parseAlgoliaHitHighlight'; +import { ParseAlgoliaHitParams } from './ParseAlgoliaHitParams'; +import { ParsedAttribute } from './ParsedAttribute'; +import { reverseHighlightedParts } from './reverseHighlightedParts'; + +export function parseAlgoliaHitReverseHighlight>( + props: ParseAlgoliaHitParams +): ParsedAttribute[] { + return reverseHighlightedParts(parseAlgoliaHitHighlight(props)); +} diff --git a/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitReverseSnippet.ts b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitReverseSnippet.ts new file mode 100644 index 000000000..6d6abec54 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitReverseSnippet.ts @@ -0,0 +1,12 @@ +import { Hit } from '@algolia/client-search'; + +import { ParseAlgoliaHitParams } from './ParseAlgoliaHitParams'; +import { parseAlgoliaHitSnippet } from './parseAlgoliaHitSnippet'; +import { ParsedAttribute } from './ParsedAttribute'; +import { reverseHighlightedParts } from './reverseHighlightedParts'; + +export function parseAlgoliaHitReverseSnippet>( + props: ParseAlgoliaHitParams +): ParsedAttribute[] { + return reverseHighlightedParts(parseAlgoliaHitSnippet(props)); +} diff --git a/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitSnippet.ts b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitSnippet.ts new file mode 100644 index 000000000..81f8b8b22 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/parseAlgoliaHitSnippet.ts @@ -0,0 +1,22 @@ +import { Hit } from '@algolia/client-search'; + +import { getAttributeValueByPath } from './getAttributeValueByPath'; +import { ParseAlgoliaHitParams } from './ParseAlgoliaHitParams'; +import { parseAttribute } from './parseAttribute'; +import { ParsedAttribute } from './ParsedAttribute'; + +export function parseAlgoliaHitSnippet>({ + hit, + attribute, + ignoreEscape, +}: ParseAlgoliaHitParams): ParsedAttribute[] { + const highlightedValue = getAttributeValueByPath( + hit, + `_snippetResult.${attribute}.value` + ); + + return parseAttribute({ + highlightedValue, + ignoreEscape, + }); +} diff --git a/packages/autocomplete-preset-algolia/src/highlight/parseAttribute.ts b/packages/autocomplete-preset-algolia/src/highlight/parseAttribute.ts new file mode 100644 index 000000000..8e98af687 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/parseAttribute.ts @@ -0,0 +1,59 @@ +import { HIGHLIGHT_PRE_TAG, HIGHLIGHT_POST_TAG } from '../constants'; + +import { ParsedAttribute } from './ParsedAttribute'; + +const htmlEscapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', +}; + +type ParseAttributeParams = { + highlightedValue: string; + ignoreEscape?: string[]; +}; + +export function parseAttribute({ + highlightedValue, + ignoreEscape = [], +}: ParseAttributeParams): ParsedAttribute[] { + const unescapedHtmlRegex = new RegExp( + `[${Object.keys(htmlEscapes) + .filter((character) => ignoreEscape.indexOf(character) === -1) + .join('')}]`, + 'g' + ); + const hasUnescapedHtmlRegex = RegExp(unescapedHtmlRegex.source); + + function escape(value: string) { + return hasUnescapedHtmlRegex.test(value) + ? value.replace(unescapedHtmlRegex, (key) => htmlEscapes[key]) + : value; + } + + const splitByPreTag = highlightedValue.split(HIGHLIGHT_PRE_TAG); + const firstValue = splitByPreTag.shift(); + const elements = !firstValue + ? [] + : [{ value: escape(firstValue), isHighlighted: false }]; + + splitByPreTag.forEach((split) => { + const splitByPostTag = split.split(HIGHLIGHT_POST_TAG); + + elements.push({ + value: escape(splitByPostTag[0]), + isHighlighted: true, + }); + + if (splitByPostTag[1] !== '') { + elements.push({ + value: escape(splitByPostTag[1]), + isHighlighted: false, + }); + } + }); + + return elements; +} diff --git a/packages/autocomplete-preset-algolia/src/highlight/reverseHighlightedParts.ts b/packages/autocomplete-preset-algolia/src/highlight/reverseHighlightedParts.ts new file mode 100644 index 000000000..37bd5520e --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/highlight/reverseHighlightedParts.ts @@ -0,0 +1,10 @@ +import { ParsedAttribute } from './ParsedAttribute'; + +export function reverseHighlightedParts(parts: ParsedAttribute[]) { + // We don't want to highlight the whole word when no parts match. + if (!parts.some((part) => part.isHighlighted)) { + return parts.map((part) => ({ ...part, isHighlighted: false })); + } + + return parts.map((part) => ({ ...part, isHighlighted: !part.isHighlighted })); +} diff --git a/packages/autocomplete-preset-algolia/src/index.ts b/packages/autocomplete-preset-algolia/src/index.ts index 8606f4067..0c8119f2a 100644 --- a/packages/autocomplete-preset-algolia/src/index.ts +++ b/packages/autocomplete-preset-algolia/src/index.ts @@ -1,7 +1,6 @@ -export { - parseAlgoliaHitHighlight, - parseAlgoliaHitReverseHighlight, - parseAlgoliaHitSnippet, - parseAlgoliaHitReverseSnippet, -} from './highlight'; -export { getAlgoliaHits, getAlgoliaResults } from './results'; +export * from './search/getAlgoliaHits'; +export * from './search/getAlgoliaResults'; +export * from './highlight/parseAlgoliaHitHighlight'; +export * from './highlight/parseAlgoliaHitReverseHighlight'; +export * from './highlight/parseAlgoliaHitReverseSnippet'; +export * from './highlight/parseAlgoliaHitSnippet'; diff --git a/packages/autocomplete-preset-algolia/src/results.ts b/packages/autocomplete-preset-algolia/src/results.ts deleted file mode 100644 index 1f7ed2954..000000000 --- a/packages/autocomplete-preset-algolia/src/results.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - Hit, - MultipleQueriesQuery, - MultipleQueriesResponse, -} from '@algolia/client-search'; -import { SearchClient } from 'algoliasearch/lite'; - -import { HIGHLIGHT_PRE_TAG, HIGHLIGHT_POST_TAG } from './constants'; -import { version } from './version'; - -interface GetAlgoliaSourceParams { - searchClient: SearchClient; - queries: MultipleQueriesQuery[]; -} - -function getAlgoliaSource({ - searchClient, - queries, -}: GetAlgoliaSourceParams) { - if (typeof searchClient.addAlgoliaAgent === 'function') { - searchClient.addAlgoliaAgent('autocomplete-core', version); - } - - return searchClient.search( - queries.map((searchParameters) => { - const { indexName, query, params } = searchParameters; - - return { - indexName, - query, - params: { - hitsPerPage: 5, - highlightPreTag: HIGHLIGHT_PRE_TAG, - highlightPostTag: HIGHLIGHT_POST_TAG, - ...params, - }, - }; - }) - ); -} - -export function getAlgoliaResults({ - searchClient, - queries, -}: GetAlgoliaSourceParams): Promise['results']> { - return getAlgoliaSource({ searchClient, queries }).then((response) => { - return response.results; - }); -} - -export function getAlgoliaHits({ - searchClient, - queries, -}: GetAlgoliaSourceParams): Promise>>> { - return getAlgoliaSource({ searchClient, queries }).then((response) => { - const results = response.results; - - return results.map((result) => result.hits); - }); -} diff --git a/packages/autocomplete-preset-algolia/src/__tests__/results.test.ts b/packages/autocomplete-preset-algolia/src/search/__tests__/search.test.ts similarity index 65% rename from packages/autocomplete-preset-algolia/src/__tests__/results.test.ts rename to packages/autocomplete-preset-algolia/src/search/__tests__/search.test.ts index 199680a8e..a8ad3ae28 100644 --- a/packages/autocomplete-preset-algolia/src/__tests__/results.test.ts +++ b/packages/autocomplete-preset-algolia/src/search/__tests__/search.test.ts @@ -1,16 +1,61 @@ import { version } from '@algolia/autocomplete-core'; +import { + MultipleQueriesResponse, + SearchResponse, +} from '@algolia/client-search'; -import { Client, getAlgoliaHits, getAlgoliaResults } from '../results'; +import { getAlgoliaHits } from '../getAlgoliaHits'; +import { getAlgoliaResults } from '../getAlgoliaResults'; -function createSearchClient() { +function createSingleSearchResponse( + partialResponse: Partial> = {} +): SearchResponse { + const { + query = '', + page = 0, + hitsPerPage = 20, + hits = [], + nbHits = hits.length, + nbPages = Math.ceil(nbHits / hitsPerPage), + params = '', + exhaustiveNbHits = true, + exhaustiveFacetsCount = true, + processingTimeMS = 0, + ...rest + } = partialResponse; + + return { + page, + hitsPerPage, + nbHits, + nbPages, + processingTimeMS, + hits, + query, + params, + exhaustiveNbHits, + exhaustiveFacetsCount, + ...rest, + }; +} + +function createMultiSearchResponse( + ...partialReponses: Array>> +): MultipleQueriesResponse { + return { + results: partialReponses.map(createSingleSearchResponse), + }; +} + +function createSearchClient(): any { return { search: jest.fn(() => - Promise.resolve({ - results: [ - { hits: [{ label: 'Hit 1' }] }, - { hits: [{ label: 'Hit 2' }] }, - ], - }) + Promise.resolve( + createMultiSearchResponse<{ label: string }>( + { hits: [{ objectID: '1', label: 'Hit 1' }] }, + { hits: [{ objectID: '2', label: 'Hit 2' }] } + ) + ) ), }; } @@ -42,8 +87,8 @@ describe('getAlgoliaResults', () => { }, ]); expect(results).toEqual([ - { hits: [{ label: 'Hit 1' }] }, - { hits: [{ label: 'Hit 2' }] }, + expect.objectContaining({ hits: [{ objectID: '1', label: 'Hit 1' }] }), + expect.objectContaining({ hits: [{ objectID: '2', label: 'Hit 2' }] }), ]); }); @@ -80,14 +125,14 @@ describe('getAlgoliaResults', () => { }, ]); expect(results).toEqual([ - { hits: [{ label: 'Hit 1' }] }, - { hits: [{ label: 'Hit 2' }] }, + expect.objectContaining({ hits: [{ objectID: '1', label: 'Hit 1' }] }), + expect.objectContaining({ hits: [{ objectID: '2', label: 'Hit 2' }] }), ]); }); test('attaches Algolia agent', async () => { const searchClient = createSearchClient(); - (searchClient as Client).addAlgoliaAgent = jest.fn(); + searchClient.addAlgoliaAgent = jest.fn(); await getAlgoliaResults({ searchClient, @@ -105,8 +150,8 @@ describe('getAlgoliaResults', () => { ], }); - expect((searchClient as Client).addAlgoliaAgent).toHaveBeenCalledTimes(1); - expect((searchClient as Client).addAlgoliaAgent).toHaveBeenCalledWith( + expect(searchClient.addAlgoliaAgent).toHaveBeenCalledTimes(1); + expect(searchClient.addAlgoliaAgent).toHaveBeenCalledWith( 'autocomplete-core', version ); @@ -139,7 +184,10 @@ describe('getAlgoliaHits', () => { }, }, ]); - expect(hits).toEqual([[{ label: 'Hit 1' }], [{ label: 'Hit 2' }]]); + expect(hits).toEqual([ + [{ objectID: '1', label: 'Hit 1' }], + [{ objectID: '2', label: 'Hit 2' }], + ]); }); test('with custom search parameters', async () => { @@ -174,12 +222,15 @@ describe('getAlgoliaHits', () => { }, }, ]); - expect(hits).toEqual([[{ label: 'Hit 1' }], [{ label: 'Hit 2' }]]); + expect(hits).toEqual([ + [{ objectID: '1', label: 'Hit 1' }], + [{ objectID: '2', label: 'Hit 2' }], + ]); }); test('attaches Algolia agent', async () => { const searchClient = createSearchClient(); - (searchClient as Client).addAlgoliaAgent = jest.fn(); + searchClient.addAlgoliaAgent = jest.fn(); await getAlgoliaHits({ searchClient, @@ -197,8 +248,8 @@ describe('getAlgoliaHits', () => { ], }); - expect((searchClient as Client).addAlgoliaAgent).toHaveBeenCalledTimes(1); - expect((searchClient as Client).addAlgoliaAgent).toHaveBeenCalledWith( + expect(searchClient.addAlgoliaAgent).toHaveBeenCalledTimes(1); + expect(searchClient.addAlgoliaAgent).toHaveBeenCalledWith( 'autocomplete-core', version ); diff --git a/packages/autocomplete-preset-algolia/src/search/getAlgoliaHits.ts b/packages/autocomplete-preset-algolia/src/search/getAlgoliaHits.ts new file mode 100644 index 000000000..4d68186ae --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/search/getAlgoliaHits.ts @@ -0,0 +1,14 @@ +import { Hit } from '@algolia/client-search'; + +import { search, SearchParams } from './search'; + +export function getAlgoliaHits({ + searchClient, + queries, +}: SearchParams): Promise>>> { + return search({ searchClient, queries }).then((response) => { + const results = response.results; + + return results.map((result) => result.hits); + }); +} diff --git a/packages/autocomplete-preset-algolia/src/search/getAlgoliaResults.ts b/packages/autocomplete-preset-algolia/src/search/getAlgoliaResults.ts new file mode 100644 index 000000000..9231c8af4 --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/search/getAlgoliaResults.ts @@ -0,0 +1,12 @@ +import { MultipleQueriesResponse } from '@algolia/client-search'; + +import { search, SearchParams } from './search'; + +export function getAlgoliaResults({ + searchClient, + queries, +}: SearchParams): Promise['results']> { + return search({ searchClient, queries }).then((response) => { + return response.results; + }); +} diff --git a/packages/autocomplete-preset-algolia/src/search/search.ts b/packages/autocomplete-preset-algolia/src/search/search.ts new file mode 100644 index 000000000..163a11f2b --- /dev/null +++ b/packages/autocomplete-preset-algolia/src/search/search.ts @@ -0,0 +1,33 @@ +import { MultipleQueriesQuery } from '@algolia/client-search'; +import { SearchClient } from 'algoliasearch/lite'; + +import { HIGHLIGHT_PRE_TAG, HIGHLIGHT_POST_TAG } from '../constants'; +import { version } from '../version'; + +export interface SearchParams { + searchClient: SearchClient; + queries: MultipleQueriesQuery[]; +} + +export function search({ searchClient, queries }: SearchParams) { + if (typeof searchClient.addAlgoliaAgent === 'function') { + searchClient.addAlgoliaAgent('autocomplete-core', version); + } + + return searchClient.search( + queries.map((searchParameters) => { + const { indexName, query, params } = searchParameters; + + return { + indexName, + query, + params: { + hitsPerPage: 5, + highlightPreTag: HIGHLIGHT_PRE_TAG, + highlightPostTag: HIGHLIGHT_POST_TAG, + ...params, + }, + }; + }) + ); +}