diff --git a/packages/autocomplete-core/src/__tests__/autocomplete.test.ts b/packages/autocomplete-core/src/__tests__/getAutocompleteSetters.test.ts similarity index 64% rename from packages/autocomplete-core/src/__tests__/autocomplete.test.ts rename to packages/autocomplete-core/src/__tests__/getAutocompleteSetters.test.ts index 8bc3d777d..4fa93af14 100644 --- a/packages/autocomplete-core/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-core/src/__tests__/getAutocompleteSetters.test.ts @@ -1,6 +1,8 @@ -import { createAutocomplete } from '..'; +import { createAutocomplete, AutocompleteSuggestion } from '..'; -function createSuggestion(items = []) { +function createSuggestion( + items: TItem[] | TItem[][] = [] +): AutocompleteSuggestion | AutocompleteSuggestion { return { source: { getInputValue: ({ suggestion }) => suggestion.label, @@ -57,22 +59,56 @@ describe('createAutocomplete', () => { ); }); - test('setSuggestions', () => { - const onStateChange = jest.fn(); - const { setSuggestions } = createAutocomplete({ - getSources: () => [], - onStateChange, + describe('setSuggestions', () => { + test('default', () => { + const onStateChange = jest.fn(); + const { setSuggestions } = createAutocomplete({ + getSources: () => [], + onStateChange, + }); + + setSuggestions([createSuggestion([{ label: 'hi' }])]); + + expect(onStateChange).toHaveBeenCalledTimes(1); + expect(onStateChange).toHaveBeenCalledWith({ + prevState: expect.any(Object), + state: expect.objectContaining({ + suggestions: [ + { + items: [ + { + label: 'hi', + __autocomplete_id: 0, + }, + ], + source: expect.any(Object), + }, + ], + }), + }); }); - const suggestions = [createSuggestion()]; - setSuggestions(suggestions); + test('flattens suggestions', () => { + const onStateChange = jest.fn(); + const { setSuggestions } = createAutocomplete({ + openOnFocus: true, + getSources: () => [], + onStateChange, + }); - expect(onStateChange).toHaveBeenCalledTimes(1); - expect(onStateChange).toHaveBeenCalledWith( - expect.objectContaining({ - state: expect.objectContaining({ suggestions }), - }) - ); + setSuggestions([createSuggestion([[{ label: 'hi' }]])]); + + expect(onStateChange).toHaveBeenCalledWith({ + prevState: expect.any(Object), + state: expect.objectContaining({ + suggestions: [ + expect.objectContaining({ + items: [{ label: 'hi', __autocomplete_id: 0 }], + }), + ], + }), + }); + }); }); test('setIsOpen', () => { diff --git a/packages/autocomplete-core/src/__tests__/utils.test.ts b/packages/autocomplete-core/src/__tests__/utils.test.ts new file mode 100644 index 000000000..dbac409c6 --- /dev/null +++ b/packages/autocomplete-core/src/__tests__/utils.test.ts @@ -0,0 +1,15 @@ +import { flatten } from '../utils'; + +describe('flatten', () => { + it('does not split strings', () => { + expect(flatten(['value', 'value'])).toEqual(['value', 'value']); + }); + + it('spreads arrays', () => { + expect(flatten(['value', ['value']])).toEqual(['value', 'value']); + }); + + it('ignores empty arrays', () => { + expect(flatten([[], 'value', 'value'])).toEqual(['value', 'value']); + }); +}); diff --git a/packages/autocomplete-core/src/getAutocompleteSetters.ts b/packages/autocomplete-core/src/getAutocompleteSetters.ts index 780d70ea7..c6d2efe69 100644 --- a/packages/autocomplete-core/src/getAutocompleteSetters.ts +++ b/packages/autocomplete-core/src/getAutocompleteSetters.ts @@ -1,4 +1,5 @@ import { AutocompleteApi, AutocompleteStore } from './types'; +import { flatten } from './utils'; interface GetAutocompleteSettersOptions { store: AutocompleteStore; @@ -23,7 +24,9 @@ export function getAutocompleteSetters({ let baseItemId = 0; const value = rawValue.map((suggestion) => ({ ...suggestion, - items: suggestion.items.map((item) => ({ + // We flatten the stored items to support calling `getAlgoliaHits` + // from the source itself. + items: flatten(suggestion.items).map((item) => ({ ...item, __autocomplete_id: baseItemId++, })), diff --git a/packages/autocomplete-core/src/getDefaultProps.ts b/packages/autocomplete-core/src/getDefaultProps.ts index 5d73722d4..57d9f41e7 100644 --- a/packages/autocomplete-core/src/getDefaultProps.ts +++ b/packages/autocomplete-core/src/getDefaultProps.ts @@ -4,6 +4,7 @@ import { getItemsCount, noop, getNormalizedSources, + flatten, } from './utils'; export function getDefaultProps( @@ -62,11 +63,7 @@ export function getDefaultProps( ) ) .then((nested) => - // same as `nested.flat()` - nested.reduce((acc, array) => { - acc = acc.concat(array); - return acc; - }, []) + flatten(nested) ) .then((sources) => sources.map((source) => ({ diff --git a/packages/autocomplete-core/src/types/api.ts b/packages/autocomplete-core/src/types/api.ts index 487e7876d..4b0087fbe 100644 --- a/packages/autocomplete-core/src/types/api.ts +++ b/packages/autocomplete-core/src/types/api.ts @@ -93,7 +93,9 @@ export interface AutocompleteSource { /** * Function called when the input changes. You can use this function to filter/search the items based on the query. */ - getSuggestions(params: GetSourcesParams): MaybePromise; + getSuggestions( + params: GetSourcesParams + ): MaybePromise; /** * Function called when an item is selected. */ diff --git a/packages/autocomplete-core/src/utils.ts b/packages/autocomplete-core/src/utils.ts index 9a39780fa..1bd98d723 100644 --- a/packages/autocomplete-core/src/utils.ts +++ b/packages/autocomplete-core/src/utils.ts @@ -187,3 +187,9 @@ export function getHighlightedItem({ export function isOrContainsNode(parent: Node, child: Node) { return parent === child || (parent.contains && parent.contains(child)); } + +export function flatten(values: Array): TType[] { + return values.reduce((a, b) => { + return a.concat(b); + }, []); +}