From e019b4dd7968f23ba500235e866e74f05fbed9de Mon Sep 17 00:00:00 2001 From: Sarah Dayan Date: Wed, 16 Jun 2021 10:46:32 +0200 Subject: [PATCH] fix(core): trigger invariant when user doesn't return anything from `getItems` (#607) * fix: trigger invariant when user doesn't return anything from getItems * tests: test invariant when not returning from getItems * fix: remove pre-resolve invariant * feat: check that every item is truthy in post-resolve invariant * fix: rmeove unused dependency * feat: add sourceId in error message * feat: use second invariant for undefined items * feat: add link to docs in invariant --- .../src/__tests__/getSources.test.ts | 22 ++++++++++++++++++ packages/autocomplete-core/src/resolve.ts | 23 ++++++++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/autocomplete-core/src/__tests__/getSources.test.ts b/packages/autocomplete-core/src/__tests__/getSources.test.ts index 686bc03d0..9cbd5b795 100644 --- a/packages/autocomplete-core/src/__tests__/getSources.test.ts +++ b/packages/autocomplete-core/src/__tests__/getSources.test.ts @@ -6,6 +6,7 @@ import { runAllMicroTasks, } from '../../../../test/utils'; import { createAutocomplete } from '../createAutocomplete'; +import * as handlers from '../onInput'; describe('getSources', () => { test('gets calls on input', () => { @@ -110,4 +111,25 @@ describe('getSources', () => { expect(onStateChange.mock.calls.pop()[0].state.collections).toHaveLength(2); }); + + test('with nothing returned from getItems throws', async () => { + const spy = jest.spyOn(handlers, 'onInput'); + + const { inputElement } = createPlayground(createAutocomplete, { + getSources() { + return [createSource({ sourceId: 'source1', getItems: () => {} })]; + }, + }); + + await expect(() => { + inputElement.focus(); + userEvent.type(inputElement, 'a'); + + return spy.mock.results[0].value; + }).rejects.toThrow( + '[Autocomplete] The `getItems` function from source "source1" must return an array of items but returned undefined.\n\nDid you forget to return items?\n\nSee: https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/sources/#param-getitems' + ); + + spy.mockRestore(); + }); }); diff --git a/packages/autocomplete-core/src/resolve.ts b/packages/autocomplete-core/src/resolve.ts index 1505f77cd..9e83ed186 100644 --- a/packages/autocomplete-core/src/resolve.ts +++ b/packages/autocomplete-core/src/resolve.ts @@ -27,7 +27,7 @@ function isDescription( function isRequesterDescription( description: TItem[] | TItem[][] | RequesterDescription ): description is RequesterDescription { - return Boolean((description as RequesterDescription).execute); + return Boolean((description as RequesterDescription)?.execute); } type PackedDescription = { @@ -172,9 +172,26 @@ export function postResolve( invariant( Array.isArray(items), - `The \`getItems\` function must return an array of items but returned type ${JSON.stringify( + `The \`getItems\` function from source "${ + source.sourceId + }" must return an array of items but returned type ${JSON.stringify( typeof items - )}:\n\n${JSON.stringify(items, null, 2)}` + )}:\n\n${JSON.stringify(items, null, 2)}. + +See: https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/sources/#param-getitems` + ); + + invariant( + (items as Array).every(Boolean), + `The \`getItems\` function from source "${ + source.sourceId + }" must return an array of items but returned ${JSON.stringify( + undefined + )}. + +Did you forget to return items? + +See: https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/sources/#param-getitems` ); return {