Skip to content

Commit ce35fea

Browse files
feat(sourceId): Add sourceId to provide data-autocomplete-source-id on section source container (#429)
* Implements required source identifier `sourceId` used as value for the `data-autocomplete-source-id` attribute of the source `section` container * Update docs * Update tests * add reference to `sourceId` and `data-autocomplete-source-id` in autocomplete-js Co-authored-by: François Chalifour <francoischalifour@users.noreply.github.com>
1 parent 18d48c4 commit ce35fea

File tree

21 files changed

+135
-16
lines changed

21 files changed

+135
-16
lines changed

examples/js/app.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ autocomplete({
6262

6363
return [
6464
{
65+
sourceId: 'products',
6566
getItems() {
6667
return getAlgoliaHits<Product>({
6768
searchClient,

examples/js/shortcutsPlugin.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const shortcutsPlugin: AutocompletePlugin<DarkModeItem> = {
1414

1515
return [
1616
{
17+
sourceId: 'shortcutsPlugin',
1718
getItems() {
1819
return [
1920
{

packages/autocomplete-core/src/__tests__/concurrency.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import userEvent from '@testing-library/user-event';
22

3-
import { defer } from '../../../../test/utils';
3+
import { createSource, defer } from '../../../../test/utils';
44
import { createAutocomplete } from '../createAutocomplete';
55

66
describe.skip('concurrency', () => {
@@ -14,11 +14,11 @@ describe.skip('concurrency', () => {
1414

1515
return defer(() => {
1616
return [
17-
{
17+
createSource({
1818
getItems() {
1919
return [{ label: query }];
2020
},
21-
},
21+
}),
2222
];
2323
}, delays[deferCount]);
2424
};

packages/autocomplete-core/src/__tests__/getInputProps.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,7 @@ describe('getInputProps', () => {
10491049
{ label: '2', url: '#2' },
10501050
],
10511051
source: {
1052+
sourceId: expect.any(String),
10521053
getItemInputValue: expect.any(Function),
10531054
getItemUrl: expect.any(Function),
10541055
getItems: expect.any(Function),

packages/autocomplete-core/src/__tests__/getSources.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ describe('getSources', () => {
4747
getSources: () => {
4848
return [
4949
{
50+
sourceId: 'testSource',
5051
getItems() {
5152
return [];
5253
},
@@ -78,6 +79,7 @@ describe('getSources', () => {
7879
templates: expect.objectContaining({
7980
item: expect.any(Function),
8081
}),
82+
sourceId: expect.any(String),
8183
},
8284
}),
8385
]),
@@ -92,6 +94,7 @@ describe('getSources', () => {
9294
getSources: () => {
9395
return [
9496
{
97+
sourceId: 'pluginSource',
9598
getItems() {
9699
return [];
97100
},
@@ -107,6 +110,7 @@ describe('getSources', () => {
107110
getSources: () => {
108111
return [
109112
{
113+
sourceId: 'testSource',
110114
getItems() {
111115
return [];
112116
},

packages/autocomplete-core/src/__tests__/stallThreshold.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import userEvent from '@testing-library/user-event';
22

3-
import { defer } from '../../../../test/utils';
3+
import { createSource, defer } from '../../../../test/utils';
44
import { createAutocomplete } from '../createAutocomplete';
55

66
describe('stallThreshold', () => {
@@ -11,11 +11,11 @@ describe('stallThreshold', () => {
1111
getSources() {
1212
return defer(() => {
1313
return [
14-
{
14+
createSource({
1515
getItems() {
1616
return [{ label: '1' }, { label: 2 }];
1717
},
18-
},
18+
}),
1919
];
2020
}, 500);
2121
},
@@ -59,11 +59,11 @@ describe('stallThreshold', () => {
5959
getSources() {
6060
return defer(() => {
6161
return [
62-
{
62+
createSource({
6363
getItems() {
6464
return [{ label: '1' }, { label: 2 }];
6565
},
66-
},
66+
}),
6767
];
6868
}, 500);
6969
},

packages/autocomplete-core/src/types/AutocompleteSource.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ export interface AutocompleteSource<TItem extends BaseItem> {
5858
* You can trigger different behaviors based on the event `type`.
5959
*/
6060
onActive?(params: OnActiveParams<TItem>): void;
61+
/**
62+
* Identifier for the source.
63+
*/
64+
sourceId: string;
6165
}
6266

6367
export type InternalAutocompleteSource<TItem extends BaseItem> = {

packages/autocomplete-core/src/utils/__tests__/getNormalizedSources.test.ts

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getNormalizedSources } from '../getNormalizedSources';
77

88
describe('getNormalizedSources', () => {
99
test('returns a promise of sources', async () => {
10-
const getSources = () => [{ getItems: () => [] }];
10+
const getSources = () => [{ sourceId: 'testSource', getItems: () => [] }];
1111
const params = {
1212
query: '',
1313
state: createState({
@@ -23,12 +23,17 @@ describe('getNormalizedSources', () => {
2323
getItems: expect.any(Function),
2424
onActive: expect.any(Function),
2525
onSelect: expect.any(Function),
26+
sourceId: 'testSource',
2627
},
2728
]);
2829
});
2930

3031
test('filters out falsy sources', async () => {
31-
const getSources = () => [{ getItems: () => [] }, false, undefined];
32+
const getSources = () => [
33+
{ sourceId: 'testSource', getItems: () => [] },
34+
false,
35+
undefined,
36+
];
3237
const params = {
3338
query: '',
3439
state: createState({
@@ -44,6 +49,7 @@ describe('getNormalizedSources', () => {
4449
getItems: expect.any(Function),
4550
onActive: expect.any(Function),
4651
onSelect: expect.any(Function),
52+
sourceId: 'testSource',
4753
},
4854
]);
4955
});
@@ -64,8 +70,55 @@ describe('getNormalizedSources', () => {
6470
);
6571
});
6672

73+
test('with missing `sourceId` triggers invariant', async () => {
74+
const getSources = () => [
75+
{
76+
getItems() {
77+
return [];
78+
},
79+
templates: {
80+
item() {},
81+
},
82+
},
83+
];
84+
const params = {
85+
query: '',
86+
state: createState({}),
87+
...createScopeApi(),
88+
};
89+
90+
// @ts-expect-error
91+
await expect(getNormalizedSources(getSources, params)).rejects.toEqual(
92+
new Error('[Autocomplete] A source must provide a `sourceId` string.')
93+
);
94+
});
95+
96+
test('with wrong `sourceId` type triggers invariant', async () => {
97+
const getSources = () => [
98+
{
99+
sourceId: ['testSource'],
100+
getItems() {
101+
return [];
102+
},
103+
templates: {
104+
item() {},
105+
},
106+
},
107+
];
108+
const params = {
109+
query: '',
110+
state: createState({}),
111+
...createScopeApi(),
112+
};
113+
114+
// @ts-expect-error
115+
await expect(getNormalizedSources(getSources, params)).rejects.toEqual(
116+
new Error('[Autocomplete] A source must provide a `sourceId` string.')
117+
);
118+
});
119+
67120
test('provides a default implementation for getItemInputValue which returns the query', async () => {
68-
const getSources = () => [{ getItems: () => [] }];
121+
const getSources = () => [{ sourceId: 'testSource', getItems: () => [] }];
69122
const params = {
70123
query: '',
71124
state: createState({
@@ -82,7 +135,7 @@ describe('getNormalizedSources', () => {
82135
});
83136

84137
test('provides a default implementation for getItemUrl', async () => {
85-
const getSources = () => [{ getItems: () => [] }];
138+
const getSources = () => [{ sourceId: 'testSource', getItems: () => [] }];
86139
const params = {
87140
query: '',
88141
state: createState({}),
@@ -97,7 +150,7 @@ describe('getNormalizedSources', () => {
97150
});
98151

99152
test('provides a default implementation for onSelect', async () => {
100-
const getSources = () => [{ getItems: () => [] }];
153+
const getSources = () => [{ sourceId: 'testSource', getItems: () => [] }];
101154
const params = {
102155
query: '',
103156
state: createState({}),
@@ -119,7 +172,7 @@ describe('getNormalizedSources', () => {
119172
});
120173

121174
test('provides a default implementation for onActive', async () => {
122-
const getSources = () => [{ getItems: () => [] }];
175+
const getSources = () => [{ sourceId: 'testSource', getItems: () => [] }];
123176
const params = {
124177
query: '',
125178
state: createState({}),

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ export function getNormalizedSources<TItem extends BaseItem>(
3232
Boolean(maybeSource)
3333
)
3434
.map((source) => {
35+
invariant(
36+
typeof source.sourceId === 'string',
37+
'A source must provide a `sourceId` string.'
38+
);
39+
3540
const normalizedSource: InternalAutocompleteSource<TItem> = {
3641
getItemInputValue({ state }) {
3742
return state.query;

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe('autocomplete-js', () => {
1111
getSources() {
1212
return [
1313
{
14+
sourceId: 'testSource',
1415
getItems() {
1516
return [
1617
{ label: 'Item 1' },
@@ -167,6 +168,7 @@ describe('autocomplete-js', () => {
167168
getSources() {
168169
return [
169170
{
171+
sourceId: 'testSource',
170172
getItems() {
171173
return [];
172174
},
@@ -206,6 +208,7 @@ describe('autocomplete-js', () => {
206208
getSources() {
207209
return [
208210
{
211+
sourceId: 'testSource',
209212
getItems() {
210213
return [];
211214
},
@@ -245,6 +248,7 @@ describe('autocomplete-js', () => {
245248
getSources() {
246249
return [
247250
{
251+
sourceId: 'testSource',
248252
getItems() {
249253
return [];
250254
},
@@ -290,6 +294,7 @@ describe('autocomplete-js', () => {
290294
getSources() {
291295
return [
292296
{
297+
sourceId: 'testSource',
293298
getItems() {
294299
return [];
295300
},
@@ -338,6 +343,7 @@ describe('autocomplete-js', () => {
338343
getSources() {
339344
return [
340345
{
346+
sourceId: 'testSource',
341347
getItems() {
342348
return [];
343349
},
@@ -383,6 +389,7 @@ describe('autocomplete-js', () => {
383389
getSources() {
384390
return [
385391
{
392+
sourceId: 'testSource',
386393
getItems() {
387394
return [
388395
{ label: 'Item 1' },
@@ -418,6 +425,7 @@ describe('autocomplete-js', () => {
418425
getSources() {
419426
return [
420427
{
428+
sourceId: 'testSource',
421429
getItems() {
422430
return [
423431
{ label: 'Item 1' },
@@ -449,6 +457,7 @@ describe('autocomplete-js', () => {
449457
getSources() {
450458
return [
451459
{
460+
sourceId: 'testSource',
452461
getItems() {
453462
return [
454463
{ label: 'Item 1' },
@@ -482,6 +491,7 @@ describe('autocomplete-js', () => {
482491
getSources() {
483492
return [
484493
{
494+
sourceId: 'testSource',
485495
getItems() {
486496
return [
487497
{ label: 'Item 1' },
@@ -512,6 +522,7 @@ describe('autocomplete-js', () => {
512522
getSources() {
513523
return [
514524
{
525+
sourceId: 'testSource',
515526
getItems() {
516527
return [
517528
{ label: 'Item 1' },

0 commit comments

Comments
 (0)