diff --git a/packages/autocomplete-core/src/getDefaultProps.ts b/packages/autocomplete-core/src/getDefaultProps.ts index 3ef7b4d0b..407e26388 100644 --- a/packages/autocomplete-core/src/getDefaultProps.ts +++ b/packages/autocomplete-core/src/getDefaultProps.ts @@ -1,4 +1,7 @@ -import { getItemsCount } from '@algolia/autocomplete-shared'; +import { + getItemsCount, + generateAutocompleteId, +} from '@algolia/autocomplete-shared'; import { AutocompleteEnvironment, @@ -7,7 +10,7 @@ import { BaseItem, InternalAutocompleteOptions, } from './types'; -import { generateAutocompleteId, getNormalizedSources, flatten } from './utils'; +import { getNormalizedSources, flatten } from './utils'; export function getDefaultProps( props: AutocompleteOptions, @@ -29,7 +32,7 @@ export function getDefaultProps( shouldPanelOpen: ({ state }) => getItemsCount(state) > 0, ...props, // Since `generateAutocompleteId` triggers a side effect (it increments - // and internal counter), we don't want to execute it if unnecessary. + // an internal counter), we don't want to execute it if unnecessary. id: props.id ?? generateAutocompleteId(), plugins, // The following props need to be deeply defaulted. diff --git a/packages/autocomplete-core/src/utils/index.ts b/packages/autocomplete-core/src/utils/index.ts index c5d499066..4a077c480 100644 --- a/packages/autocomplete-core/src/utils/index.ts +++ b/packages/autocomplete-core/src/utils/index.ts @@ -1,6 +1,5 @@ export * from './createConcurrentSafePromise'; export * from './flatten'; -export * from './generateAutocompleteId'; export * from './getNextActiveItemId'; export * from './getNormalizedSources'; export * from './getActiveItem'; diff --git a/packages/autocomplete-js/src/__tests__/api.test.ts b/packages/autocomplete-js/src/__tests__/api.test.ts index 5ace13785..21313204a 100644 --- a/packages/autocomplete-js/src/__tests__/api.test.ts +++ b/packages/autocomplete-js/src/__tests__/api.test.ts @@ -1,4 +1,5 @@ import { createAutocomplete } from '@algolia/autocomplete-core'; +import { waitFor } from '@testing-library/dom'; import { createCollection } from '../../../../test/utils'; import { autocomplete } from '../autocomplete'; @@ -253,6 +254,23 @@ describe('api', () => { }) ); }); + + test('overrides the default id', async () => { + const container = document.createElement('div'); + + document.body.appendChild(container); + const { update } = autocomplete<{ label: string }>({ + container, + }); + + update({ id: 'bestSearchExperience' }); + + await waitFor(() => { + expect( + document.body.querySelector('#bestSearchExperience-label') + ).toBeInTheDocument(); + }); + }); }); describe('destroy', () => { diff --git a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts index d3d84a586..07900c359 100644 --- a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts @@ -1,7 +1,18 @@ +import * as autocompleteShared from '@algolia/autocomplete-shared'; import { fireEvent, waitFor } from '@testing-library/dom'; +import { castToJestMock } from '../../../../test/utils'; import { autocomplete } from '../autocomplete'; +jest.mock('@algolia/autocomplete-shared', () => { + const module = jest.requireActual('@algolia/autocomplete-shared'); + + return { + ...module, + generateAutocompleteId: jest.fn(() => `autocomplete-test`), + }; +}); + describe('autocomplete-js', () => { test('renders with default options', () => { const container = document.createElement('div'); @@ -151,6 +162,82 @@ describe('autocomplete-js', () => { `); }); + test("renders with an auto-incremented id if there's multiple instances", () => { + const container = document.createElement('div'); + const initialNbCalls = castToJestMock( + autocompleteShared.generateAutocompleteId + ).mock.calls.length; + + document.body.appendChild(container); + autocomplete({ + container, + }); + + expect(autocompleteShared.generateAutocompleteId).toHaveBeenCalledTimes( + initialNbCalls + 1 + ); + + autocomplete({ + container, + }); + + expect(autocompleteShared.generateAutocompleteId).toHaveBeenCalledTimes( + initialNbCalls + 2 + ); + + autocomplete({ + container, + }); + + expect(autocompleteShared.generateAutocompleteId).toHaveBeenCalledTimes( + initialNbCalls + 3 + ); + }); + + test("doesn't increment the id when toggling detached mode", () => { + const container = document.createElement('div'); + + document.body.appendChild(container); + const { update } = autocomplete<{ label: string }>({ + container, + }); + + const initialNbCalls = castToJestMock( + autocompleteShared.generateAutocompleteId + ).mock.calls.length; + + expect(autocompleteShared.generateAutocompleteId).toHaveBeenCalledTimes( + initialNbCalls + ); + + const originalMatchMedia = window.matchMedia; + + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn((query) => ({ + matches: true, + media: query, + onchange: null, + addListener: jest.fn(), + removeListener: jest.fn(), + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); + + update({ detachedMediaQuery: '' }); + + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: originalMatchMedia, + }); + + expect(autocompleteShared.generateAutocompleteId).toHaveBeenCalledTimes( + initialNbCalls + ); + }); + test('renders noResults template on no results', async () => { const container = document.createElement('div'); const panelContainer = document.createElement('div'); diff --git a/packages/autocomplete-js/src/autocomplete.ts b/packages/autocomplete-js/src/autocomplete.ts index 538fbc85a..7dc5003bd 100644 --- a/packages/autocomplete-js/src/autocomplete.ts +++ b/packages/autocomplete-js/src/autocomplete.ts @@ -42,6 +42,7 @@ export function autocomplete( props.value.renderer.detachedMediaQuery ).matches ); + const autocomplete = reactive(() => createAutocomplete({ ...props.value.core, @@ -246,12 +247,12 @@ export function autocomplete( runEffect(() => { const onResize = debounce(() => { - const previousisDetached = isDetached.value; + const previousIsDetached = isDetached.value; isDetached.value = props.value.core.environment.matchMedia( props.value.renderer.detachedMediaQuery ).matches; - if (previousisDetached !== isDetached.value) { + if (previousIsDetached !== isDetached.value) { update({}); } else { requestAnimationFrame(setPanelPosition); diff --git a/packages/autocomplete-js/src/getDefaultOptions.ts b/packages/autocomplete-js/src/getDefaultOptions.ts index 2de27006a..5fe19d88d 100644 --- a/packages/autocomplete-js/src/getDefaultOptions.ts +++ b/packages/autocomplete-js/src/getDefaultOptions.ts @@ -1,5 +1,8 @@ import { BaseItem } from '@algolia/autocomplete-core'; -import { invariant } from '@algolia/autocomplete-shared'; +import { + generateAutocompleteId, + invariant, +} from '@algolia/autocomplete-shared'; import { createElement as preactCreateElement, Fragment as PreactFragment, @@ -115,6 +118,7 @@ export function getDefaultOptions( }, core: { ...core, + id: core.id ?? generateAutocompleteId(), environment, }, }; diff --git a/packages/autocomplete-core/src/utils/generateAutocompleteId.ts b/packages/autocomplete-shared/src/generateAutocompleteId.ts similarity index 100% rename from packages/autocomplete-core/src/utils/generateAutocompleteId.ts rename to packages/autocomplete-shared/src/generateAutocompleteId.ts diff --git a/packages/autocomplete-shared/src/index.ts b/packages/autocomplete-shared/src/index.ts index a857036e8..db437a688 100644 --- a/packages/autocomplete-shared/src/index.ts +++ b/packages/autocomplete-shared/src/index.ts @@ -1,5 +1,6 @@ export * from './createRef'; export * from './debounce'; +export * from './generateAutocompleteId'; export * from './getAttributeValueByPath'; export * from './getItemsCount'; export * from './invariant'; diff --git a/test/utils/castToJestMock.ts b/test/utils/castToJestMock.ts new file mode 100644 index 000000000..18d72d057 --- /dev/null +++ b/test/utils/castToJestMock.ts @@ -0,0 +1,3 @@ +export const castToJestMock = any>( + func: TFunction +) => func as jest.MockedFunction; diff --git a/test/utils/index.ts b/test/utils/index.ts index 912a9c3f6..13c4bbe7e 100644 --- a/test/utils/index.ts +++ b/test/utils/index.ts @@ -1,3 +1,4 @@ +export * from './castToJestMock'; export * from './createApiResponse'; export * from './createCollection'; export * from './createNavigator';