Skip to content

Commit fe2bf13

Browse files
authored
fix(js): prevent id incrementation when toggling detached mode (#489)
* prevent id incrementation when toggling detached mode
1 parent f83d3ec commit fe2bf13

File tree

10 files changed

+124
-7
lines changed

10 files changed

+124
-7
lines changed

packages/autocomplete-core/src/getDefaultProps.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { getItemsCount } from '@algolia/autocomplete-shared';
1+
import {
2+
getItemsCount,
3+
generateAutocompleteId,
4+
} from '@algolia/autocomplete-shared';
25

36
import {
47
AutocompleteEnvironment,
@@ -7,7 +10,7 @@ import {
710
BaseItem,
811
InternalAutocompleteOptions,
912
} from './types';
10-
import { generateAutocompleteId, getNormalizedSources, flatten } from './utils';
13+
import { getNormalizedSources, flatten } from './utils';
1114

1215
export function getDefaultProps<TItem extends BaseItem>(
1316
props: AutocompleteOptions<TItem>,
@@ -29,7 +32,7 @@ export function getDefaultProps<TItem extends BaseItem>(
2932
shouldPanelOpen: ({ state }) => getItemsCount(state) > 0,
3033
...props,
3134
// Since `generateAutocompleteId` triggers a side effect (it increments
32-
// and internal counter), we don't want to execute it if unnecessary.
35+
// an internal counter), we don't want to execute it if unnecessary.
3336
id: props.id ?? generateAutocompleteId(),
3437
plugins,
3538
// The following props need to be deeply defaulted.

packages/autocomplete-core/src/utils/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export * from './createConcurrentSafePromise';
22
export * from './flatten';
3-
export * from './generateAutocompleteId';
43
export * from './getNextActiveItemId';
54
export * from './getNormalizedSources';
65
export * from './getActiveItem';

packages/autocomplete-js/src/__tests__/api.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createAutocomplete } from '@algolia/autocomplete-core';
2+
import { waitFor } from '@testing-library/dom';
23

34
import { createCollection } from '../../../../test/utils';
45
import { autocomplete } from '../autocomplete';
@@ -253,6 +254,23 @@ describe('api', () => {
253254
})
254255
);
255256
});
257+
258+
test('overrides the default id', async () => {
259+
const container = document.createElement('div');
260+
261+
document.body.appendChild(container);
262+
const { update } = autocomplete<{ label: string }>({
263+
container,
264+
});
265+
266+
update({ id: 'bestSearchExperience' });
267+
268+
await waitFor(() => {
269+
expect(
270+
document.body.querySelector('#bestSearchExperience-label')
271+
).toBeInTheDocument();
272+
});
273+
});
256274
});
257275

258276
describe('destroy', () => {

packages/autocomplete-js/src/__tests__/autocomplete.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1+
import * as autocompleteShared from '@algolia/autocomplete-shared';
12
import { fireEvent, waitFor } from '@testing-library/dom';
23

4+
import { castToJestMock } from '../../../../test/utils';
35
import { autocomplete } from '../autocomplete';
46

7+
jest.mock('@algolia/autocomplete-shared', () => {
8+
const module = jest.requireActual('@algolia/autocomplete-shared');
9+
10+
return {
11+
...module,
12+
generateAutocompleteId: jest.fn(() => `autocomplete-test`),
13+
};
14+
});
15+
516
describe('autocomplete-js', () => {
617
test('renders with default options', () => {
718
const container = document.createElement('div');
@@ -151,6 +162,82 @@ describe('autocomplete-js', () => {
151162
`);
152163
});
153164

165+
test("renders with an auto-incremented id if there's multiple instances", () => {
166+
const container = document.createElement('div');
167+
const initialNbCalls = castToJestMock(
168+
autocompleteShared.generateAutocompleteId
169+
).mock.calls.length;
170+
171+
document.body.appendChild(container);
172+
autocomplete({
173+
container,
174+
});
175+
176+
expect(autocompleteShared.generateAutocompleteId).toHaveBeenCalledTimes(
177+
initialNbCalls + 1
178+
);
179+
180+
autocomplete({
181+
container,
182+
});
183+
184+
expect(autocompleteShared.generateAutocompleteId).toHaveBeenCalledTimes(
185+
initialNbCalls + 2
186+
);
187+
188+
autocomplete({
189+
container,
190+
});
191+
192+
expect(autocompleteShared.generateAutocompleteId).toHaveBeenCalledTimes(
193+
initialNbCalls + 3
194+
);
195+
});
196+
197+
test("doesn't increment the id when toggling detached mode", () => {
198+
const container = document.createElement('div');
199+
200+
document.body.appendChild(container);
201+
const { update } = autocomplete<{ label: string }>({
202+
container,
203+
});
204+
205+
const initialNbCalls = castToJestMock(
206+
autocompleteShared.generateAutocompleteId
207+
).mock.calls.length;
208+
209+
expect(autocompleteShared.generateAutocompleteId).toHaveBeenCalledTimes(
210+
initialNbCalls
211+
);
212+
213+
const originalMatchMedia = window.matchMedia;
214+
215+
Object.defineProperty(window, 'matchMedia', {
216+
writable: true,
217+
value: jest.fn((query) => ({
218+
matches: true,
219+
media: query,
220+
onchange: null,
221+
addListener: jest.fn(),
222+
removeListener: jest.fn(),
223+
addEventListener: jest.fn(),
224+
removeEventListener: jest.fn(),
225+
dispatchEvent: jest.fn(),
226+
})),
227+
});
228+
229+
update({ detachedMediaQuery: '' });
230+
231+
Object.defineProperty(window, 'matchMedia', {
232+
writable: true,
233+
value: originalMatchMedia,
234+
});
235+
236+
expect(autocompleteShared.generateAutocompleteId).toHaveBeenCalledTimes(
237+
initialNbCalls
238+
);
239+
});
240+
154241
test('renders noResults template on no results', async () => {
155242
const container = document.createElement('div');
156243
const panelContainer = document.createElement('div');

packages/autocomplete-js/src/autocomplete.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export function autocomplete<TItem extends BaseItem>(
4242
props.value.renderer.detachedMediaQuery
4343
).matches
4444
);
45+
4546
const autocomplete = reactive(() =>
4647
createAutocomplete<TItem>({
4748
...props.value.core,
@@ -246,12 +247,12 @@ export function autocomplete<TItem extends BaseItem>(
246247

247248
runEffect(() => {
248249
const onResize = debounce<Event>(() => {
249-
const previousisDetached = isDetached.value;
250+
const previousIsDetached = isDetached.value;
250251
isDetached.value = props.value.core.environment.matchMedia(
251252
props.value.renderer.detachedMediaQuery
252253
).matches;
253254

254-
if (previousisDetached !== isDetached.value) {
255+
if (previousIsDetached !== isDetached.value) {
255256
update({});
256257
} else {
257258
requestAnimationFrame(setPanelPosition);

packages/autocomplete-js/src/getDefaultOptions.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { BaseItem } from '@algolia/autocomplete-core';
2-
import { invariant } from '@algolia/autocomplete-shared';
2+
import {
3+
generateAutocompleteId,
4+
invariant,
5+
} from '@algolia/autocomplete-shared';
36
import {
47
createElement as preactCreateElement,
58
Fragment as PreactFragment,
@@ -115,6 +118,7 @@ export function getDefaultOptions<TItem extends BaseItem>(
115118
},
116119
core: {
117120
...core,
121+
id: core.id ?? generateAutocompleteId(),
118122
environment,
119123
},
120124
};

packages/autocomplete-shared/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './createRef';
22
export * from './debounce';
3+
export * from './generateAutocompleteId';
34
export * from './getAttributeValueByPath';
45
export * from './getItemsCount';
56
export * from './invariant';

test/utils/castToJestMock.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const castToJestMock = <TFunction extends (...args: any[]) => any>(
2+
func: TFunction
3+
) => func as jest.MockedFunction<typeof func>;

test/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './castToJestMock';
12
export * from './createApiResponse';
23
export * from './createCollection';
34
export * from './createNavigator';

0 commit comments

Comments
 (0)