Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(getSuggestions): allow nested arrays to be returned #331

Merged
merged 14 commits into from
Oct 29, 2020
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createAutocomplete } from '..';
import { createAutocomplete, AutocompleteSuggestion } from '..';

function createSuggestion(items = []) {
function createSuggestion<TItem extends { label: string }>(
items: TItem[] | TItem[][] = []
): AutocompleteSuggestion<TItem | TItem[]> | AutocompleteSuggestion<TItem[]> {
return {
source: {
getInputValue: ({ suggestion }) => suggestion.label,
Expand Down Expand Up @@ -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' }]])]);
Haroenv marked this conversation as resolved.
Show resolved Hide resolved

expect(onStateChange).toHaveBeenCalledWith({
prevState: expect.any(Object),
state: expect.objectContaining({
suggestions: [
expect.objectContaining({
items: [{ label: 'hi', __autocomplete_id: 0 }],
}),
],
}),
});
});
});

test('setIsOpen', () => {
Expand Down
15 changes: 15 additions & 0 deletions packages/autocomplete-core/src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -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']);
});
});
5 changes: 4 additions & 1 deletion packages/autocomplete-core/src/getAutocompleteSetters.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AutocompleteApi, AutocompleteStore } from './types';
import { flatten } from './utils';

interface GetAutocompleteSettersOptions<TItem> {
store: AutocompleteStore<TItem>;
Expand All @@ -23,7 +24,9 @@ export function getAutocompleteSetters<TItem>({
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) => ({
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
...item,
__autocomplete_id: baseItemId++,
})),
Expand Down
7 changes: 2 additions & 5 deletions packages/autocomplete-core/src/getDefaultProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getItemsCount,
noop,
getNormalizedSources,
flatten,
} from './utils';

export function getDefaultProps<TItem>(
Expand Down Expand Up @@ -62,11 +63,7 @@ export function getDefaultProps<TItem>(
)
)
.then((nested) =>
// same as `nested.flat()`
nested.reduce((acc, array) => {
acc = acc.concat(array);
return acc;
}, [])
flatten(nested)
)
.then((sources) =>
sources.map((source) => ({
Expand Down
4 changes: 3 additions & 1 deletion packages/autocomplete-core/src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ export interface AutocompleteSource<TItem> {
/**
* Function called when the input changes. You can use this function to filter/search the items based on the query.
*/
getSuggestions(params: GetSourcesParams<TItem>): MaybePromise<TItem[]>;
getSuggestions(
params: GetSourcesParams<TItem>
): MaybePromise<TItem[] | TItem[][]>;
/**
* Function called when an item is selected.
*/
Expand Down
6 changes: 6 additions & 0 deletions packages/autocomplete-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,9 @@ export function getHighlightedItem<TItem>({
export function isOrContainsNode(parent: Node, child: Node) {
return parent === child || (parent.contains && parent.contains(child));
}

export function flatten<TType>(values: Array<TType | TType[]>): TType[] {
return values.reduce<TType[]>((a, b) => {
return a.concat(b);
}, []);
}