Skip to content

Commit c555255

Browse files
marieglrHaroenv
andcommitted
feat(voice): add additional query parameters (#3738)
Two new arguments: - `additionalQueryParameters: () => Partial<SearchParameters>` (only applied when voice search is mounted) - `language: string` (iso 639-1), the parameter sent to Algolia will be always the short version known limitation: additional query parameters will stay applied as long as the Voice Search widget is mounted, meaning they can cause stale values if you switch input method without unmounting (limitation of the architecture of InstantSearch) Co-Authored-By: haroenv <haroen@algolia.com>
1 parent 25716fc commit c555255

File tree

5 files changed

+198
-7
lines changed

5 files changed

+198
-7
lines changed

src/connectors/voice-search/__tests__/connectVoiceSearch-test.js

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ jest.mock('../../../lib/voiceSearchHelper', () => {
1616
};
1717
});
1818

19-
function getInitializedWidget() {
19+
function getInitializedWidget({ widgetParams = {} } = {}) {
2020
const helper = algoliasearchHelper({}, '');
2121

2222
const renderFn = () => {};
2323
const makeWidget = connectVoiceSearch(renderFn);
24-
const widget = makeWidget({});
24+
const widget = makeWidget(widgetParams);
2525

2626
helper.search = () => {};
2727
widget.init({ helper });
@@ -30,7 +30,7 @@ function getInitializedWidget() {
3030
renderFn,
3131
widget,
3232
helper,
33-
refine: query => widget._refine(query),
33+
refine: widget._refine,
3434
};
3535
}
3636

@@ -277,4 +277,113 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/voice-searc
277277
);
278278
});
279279
});
280+
281+
describe('additional search parameters', () => {
282+
it('applies default search parameters if given', () => {
283+
const { helper, refine } = getInitializedWidget({
284+
widgetParams: {
285+
additionalQueryParameters: () => {},
286+
},
287+
});
288+
289+
refine('query');
290+
expect(helper.state).toEqual(
291+
new SearchParameters({
292+
ignorePlurals: true,
293+
removeStopWords: true,
294+
optionalWords: 'query',
295+
queryLanguages: undefined,
296+
index: '',
297+
query: 'query',
298+
})
299+
);
300+
});
301+
302+
it('applies queryLanguages if language given', () => {
303+
const { helper, refine } = getInitializedWidget({
304+
widgetParams: {
305+
language: 'en-US',
306+
additionalQueryParameters: () => {},
307+
},
308+
});
309+
310+
refine('query');
311+
expect(helper.state).toEqual(
312+
new SearchParameters({
313+
queryLanguages: ['en'],
314+
// regular
315+
removeStopWords: true,
316+
optionalWords: 'query',
317+
ignorePlurals: true,
318+
query: 'query',
319+
index: '',
320+
})
321+
);
322+
});
323+
324+
it('applies additional parameters if language given', () => {
325+
const { helper, refine } = getInitializedWidget({
326+
widgetParams: {
327+
additionalQueryParameters: () => ({
328+
distinct: true,
329+
}),
330+
},
331+
});
332+
333+
refine('query');
334+
expect(helper.state).toEqual(
335+
new SearchParameters({
336+
ignorePlurals: true,
337+
removeStopWords: true,
338+
optionalWords: 'query',
339+
queryLanguages: undefined,
340+
index: '',
341+
query: 'query',
342+
distinct: true,
343+
})
344+
);
345+
});
346+
347+
it('removes additional parameters when disposed', () => {
348+
const { widget, helper, refine } = getInitializedWidget({
349+
widgetParams: {
350+
additionalQueryParameters: () => {},
351+
},
352+
});
353+
354+
refine('query');
355+
const newState = widget.dispose({ state: helper.state });
356+
expect(newState).toEqual(
357+
new SearchParameters({
358+
ignorePlurals: undefined,
359+
removeStopWords: undefined,
360+
optionalWords: undefined,
361+
queryLanguages: undefined,
362+
index: '',
363+
})
364+
);
365+
});
366+
367+
it('removes additional parameters and extra parameters when disposed', () => {
368+
const { widget, helper, refine } = getInitializedWidget({
369+
widgetParams: {
370+
additionalQueryParameters: () => ({
371+
distinct: true,
372+
}),
373+
},
374+
});
375+
376+
refine('query');
377+
const newState = widget.dispose({ state: helper.state });
378+
expect(newState).toEqual(
379+
new SearchParameters({
380+
ignorePlurals: undefined,
381+
removeStopWords: undefined,
382+
optionalWords: undefined,
383+
queryLanguages: undefined,
384+
index: '',
385+
})
386+
);
387+
});
388+
});
280389
});

src/connectors/voice-search/connectVoiceSearch.ts

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { PlainSearchParameters } from 'algoliasearch-helper';
12
import {
23
checkRendering,
34
createDocumentationMessageGenerator,
@@ -15,7 +16,11 @@ const withUsage = createDocumentationMessageGenerator({
1516
});
1617

1718
export type VoiceSearchConnectorParams = {
18-
searchAsYouSpeak?: boolean;
19+
searchAsYouSpeak: boolean;
20+
language?: string;
21+
additionalQueryParameters?: (params: {
22+
query: string;
23+
}) => PlainSearchParameters | void;
1924
};
2025

2126
export interface VoiceSearchRendererOptions<TVoiceSearchWidgetParams>
@@ -71,20 +76,42 @@ const connectVoiceSearch: VoiceSearchConnector = (
7176
);
7277
};
7378

74-
const { searchAsYouSpeak = false } = widgetParams;
79+
const {
80+
searchAsYouSpeak = false,
81+
language,
82+
additionalQueryParameters,
83+
} = widgetParams;
7584

7685
return {
7786
$$type: 'ais.voiceSearch',
7887

7988
init({ helper, instantSearchInstance }) {
8089
(this as any)._refine = (query: string): void => {
8190
if (query !== helper.state.query) {
91+
const queryLanguages = language
92+
? [language.split('-')[0]]
93+
: undefined;
94+
helper.setQueryParameter('queryLanguages', queryLanguages);
95+
96+
if (typeof additionalQueryParameters === 'function') {
97+
helper.setState(
98+
helper.state.setQueryParameters({
99+
ignorePlurals: true,
100+
removeStopWords: true,
101+
// @ts-ignore (optionalWords only allows array, while string is also valid)
102+
optionalWords: query,
103+
...additionalQueryParameters({ query }),
104+
})
105+
);
106+
}
107+
82108
helper.setQuery(query).search();
83109
}
84110
};
85111

86112
(this as any)._voiceSearchHelper = createVoiceSearchHelper({
87113
searchAsYouSpeak,
114+
language,
88115
onQueryChange: query => (this as any)._refine(query),
89116
onStateChange: () => {
90117
render({
@@ -115,7 +142,26 @@ const connectVoiceSearch: VoiceSearchConnector = (
115142

116143
unmountFn();
117144

118-
return state.setQueryParameter('query', undefined);
145+
let newState = state;
146+
if (typeof additionalQueryParameters === 'function') {
147+
const additional = additionalQueryParameters({ query: '' });
148+
const toReset = additional
149+
? Object.keys(additional).reduce((acc, current) => {
150+
acc[current] = undefined;
151+
return acc;
152+
}, {})
153+
: {};
154+
newState = state.setQueryParameters({
155+
// @ts-ignore (queryLanguages is not yet added to algoliasearch)
156+
queryLanguages: undefined,
157+
ignorePlurals: undefined,
158+
removeStopWords: undefined,
159+
optionalWords: undefined,
160+
...toReset,
161+
});
162+
}
163+
164+
return newState.setQueryParameter('query', undefined);
119165
},
120166

121167
getWidgetState(uiState, { searchParameters }) {

src/lib/voiceSearchHelper/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export type VoiceSearchHelperParams = {
22
searchAsYouSpeak: boolean;
3+
language?: string;
34
onQueryChange: (query: string) => void;
45
onStateChange: () => void;
56
};
@@ -31,6 +32,7 @@ export type ToggleListening = () => void;
3132

3233
export default function createVoiceSearchHelper({
3334
searchAsYouSpeak,
35+
language,
3436
onQueryChange,
3537
onStateChange,
3638
}: VoiceSearchHelperParams): VoiceSearchHelper {
@@ -105,6 +107,11 @@ export default function createVoiceSearchHelper({
105107
}
106108
resetState('askingPermission');
107109
recognition.interimResults = true;
110+
111+
if (language) {
112+
recognition.lang = language;
113+
}
114+
108115
recognition.addEventListener('start', onStart);
109116
recognition.addEventListener('error', onError);
110117
recognition.addEventListener('result', onResult);

src/widgets/voice-search/voice-search.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { render, unmountComponentAtNode } from 'preact-compat';
22
import cx from 'classnames';
3+
import { PlainSearchParameters } from 'algoliasearch-helper';
34
import {
45
getContainerNode,
56
createDocumentationMessageGenerator,
@@ -42,6 +43,10 @@ type VoiceSearchWidgetParams = {
4243
cssClasses?: Partial<VoiceSearchCSSClasses>;
4344
templates?: Partial<VoiceSearchTemplates>;
4445
searchAsYouSpeak?: boolean;
46+
language?: string;
47+
additionalQueryParameters?: (params: {
48+
query: string;
49+
}) => PlainSearchParameters | void;
4550
};
4651

4752
interface VoiceSearchRendererWidgetParams extends VoiceSearchWidgetParams {
@@ -79,7 +84,9 @@ const voiceSearch: VoiceSearch = (
7984
container,
8085
cssClasses: userCssClasses = {} as VoiceSearchCSSClasses,
8186
templates,
82-
searchAsYouSpeak,
87+
searchAsYouSpeak = false,
88+
language,
89+
additionalQueryParameters,
8390
} = {} as VoiceSearchWidgetParams
8491
) => {
8592
if (!container) {
@@ -103,6 +110,8 @@ const voiceSearch: VoiceSearch = (
103110
cssClasses,
104111
templates: { ...defaultTemplates, ...templates },
105112
searchAsYouSpeak,
113+
language,
114+
additionalQueryParameters,
106115
});
107116
};
108117

stories/voice-search.stories.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,24 @@ storiesOf('VoiceSearch', module)
186186
})
187187
);
188188
})
189+
)
190+
.add(
191+
'with additional parameters',
192+
withHits(({ search, container }) => {
193+
const descContainer = document.createElement('div');
194+
const realContainer = document.createElement('div');
195+
container.appendChild(descContainer);
196+
container.appendChild(realContainer);
197+
descContainer.innerHTML = `
198+
<p>Sets the default additional parameters, as well as a language</p>
199+
`;
200+
201+
search.addWidget(
202+
voiceSearch({
203+
container: realContainer,
204+
language: 'en-US',
205+
additionalQueryParameters: () => {},
206+
})
207+
);
208+
})
189209
);

0 commit comments

Comments
 (0)