From 881c41416d6546e769f895bdb02f5eac231a25fa Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 8 Apr 2019 10:46:12 +0200 Subject: [PATCH 01/66] feat(voiceSearch): WIP add VoiceSearch component --- .../src/connectors/connectVoiceSearch.js | 9 + .../react-instantsearch-core/src/index.js | 4 + .../src/types/index.ts | 1 + .../src/types/translatable.ts | 1 + .../src/components/VoiceSearch.tsx | 158 ++++++++++++++++++ packages/react-instantsearch-dom/src/index.js | 1 + .../src/widgets/VoiceSearch.ts | 4 + stories/VoiceSearch.stories.js | 16 ++ 8 files changed, 194 insertions(+) create mode 100644 packages/react-instantsearch-core/src/connectors/connectVoiceSearch.js create mode 100644 packages/react-instantsearch-core/src/types/index.ts create mode 100644 packages/react-instantsearch-core/src/types/translatable.ts create mode 100644 packages/react-instantsearch-dom/src/components/VoiceSearch.tsx create mode 100644 packages/react-instantsearch-dom/src/widgets/VoiceSearch.ts create mode 100644 stories/VoiceSearch.stories.js diff --git a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.js b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.js new file mode 100644 index 0000000000..309c15184f --- /dev/null +++ b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.js @@ -0,0 +1,9 @@ +import createConnector from '../core/createConnector'; + +export default createConnector({ + displayName: 'AlgoliaVoiceSearch', + + getProvidedProps() { + return {}; + }, +}); diff --git a/packages/react-instantsearch-core/src/index.js b/packages/react-instantsearch-core/src/index.js index b81e8d8182..78105cd640 100644 --- a/packages/react-instantsearch-core/src/index.js +++ b/packages/react-instantsearch-core/src/index.js @@ -48,3 +48,7 @@ export { default as connectStats } from './connectors/connectStats'; export { default as connectToggleRefinement, } from './connectors/connectToggleRefinement'; +export { default as connectVoiceSearch } from './connectors/connectVoiceSearch'; + +// Types +export * from './types'; diff --git a/packages/react-instantsearch-core/src/types/index.ts b/packages/react-instantsearch-core/src/types/index.ts new file mode 100644 index 0000000000..0274a75363 --- /dev/null +++ b/packages/react-instantsearch-core/src/types/index.ts @@ -0,0 +1 @@ +export * from './translatable'; diff --git a/packages/react-instantsearch-core/src/types/translatable.ts b/packages/react-instantsearch-core/src/types/translatable.ts new file mode 100644 index 0000000000..05f0d9233f --- /dev/null +++ b/packages/react-instantsearch-core/src/types/translatable.ts @@ -0,0 +1 @@ +export type Translate = (key: string, ...params: string[]) => string; diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx new file mode 100644 index 0000000000..43bd9cb061 --- /dev/null +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -0,0 +1,158 @@ +import React from 'react'; +import { translatable, Translate } from 'react-instantsearch-core'; +import { createClassNames } from '../core/utils'; +const cx = createClassNames('VoiceSearch'); + +type VoiceListeningState = { + status: string; + transcript?: string; + isSpeechFinal?: boolean; + errorCode?: string; +}; + +type ToggleListening = (toggleListeningParams: { + searchAsYouSpeak: boolean; +}) => void; + +type InnerComponentProps = { + status: string; + errorCode?: string; + isListening: boolean; + transcript?: string; + isSpeechFinal?: boolean; + isSupportedBrowser: boolean; +}; + +type VoiceSearchProps = { + isSupportedBrowser: boolean; + isListening: boolean; + toggleListening: ToggleListening; + voiceListeningState: VoiceListeningState; + searchAsYouSpeak?: boolean; + + translate: Translate; + buttonComponent?: React.FunctionComponent; + statusComponent?: React.FunctionComponent; +}; + +const DefaultButtonComponent: React.FunctionComponent = ({ + status, + errorCode, + isListening, +}) => { + if (status === 'error' && errorCode === 'not-allowed') { + return ( + + + + + + + + ); + } else if (isListening) { + return ( + + + + + + + ); + } else { + return ( + + + + + + + ); + } +}; + +const DefaultStatusComponent: React.FunctionComponent = ({ + status, +}) =>

{status}

; + +const VoiceSearch = ({ + translate, + isSupportedBrowser, + isListening, + toggleListening, + voiceListeningState, + searchAsYouSpeak = false, + + buttonComponent: ButtonComponent = DefaultButtonComponent, + statusComponent: StatusComponent = DefaultStatusComponent, +}: VoiceSearchProps) => { + const onClick = (event: React.MouseEvent) => { + event.currentTarget.blur(); + toggleListening({ searchAsYouSpeak }); + }; + + const { status, transcript, isSpeechFinal, errorCode } = voiceListeningState; + + const innerProps = { + status, + errorCode, + isListening, + transcript, + isSpeechFinal, + isSupportedBrowser, + }; + + return ( +
+ +
+ +
+
+ ); +}; + +export default translatable({ + buttonTitle: 'Search by voice', +})(VoiceSearch); diff --git a/packages/react-instantsearch-dom/src/index.js b/packages/react-instantsearch-dom/src/index.js index c98d004121..d0a3507aa7 100644 --- a/packages/react-instantsearch-dom/src/index.js +++ b/packages/react-instantsearch-dom/src/index.js @@ -57,6 +57,7 @@ export { default as Snippet } from './widgets/Snippet'; export { default as SortBy } from './widgets/SortBy'; export { default as Stats } from './widgets/Stats'; export { default as ToggleRefinement } from './widgets/ToggleRefinement'; +export { default as VoiceSearch } from './widgets/VoiceSearch'; // Utils export { createClassNames } from './core/utils'; diff --git a/packages/react-instantsearch-dom/src/widgets/VoiceSearch.ts b/packages/react-instantsearch-dom/src/widgets/VoiceSearch.ts new file mode 100644 index 0000000000..66db565290 --- /dev/null +++ b/packages/react-instantsearch-dom/src/widgets/VoiceSearch.ts @@ -0,0 +1,4 @@ +import { connectVoiceSearch } from 'react-instantsearch-core'; +import VoiceSearch from '../components/VoiceSearch'; + +export default connectVoiceSearch(VoiceSearch); diff --git a/stories/VoiceSearch.stories.js b/stories/VoiceSearch.stories.js new file mode 100644 index 0000000000..6def88d449 --- /dev/null +++ b/stories/VoiceSearch.stories.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { VoiceSearch } from 'react-instantsearch-dom'; +import { WrapWithHits } from './util'; + +const stories = storiesOf('VoiceSearch', module); + +stories.add('default', () => ( + + + +)); From 9e066d07859f5daac7a57ee7f2f732e5080eb248 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 9 Apr 2019 15:59:49 +0200 Subject: [PATCH 02/66] feat(voiceSearch): WIP add voice search helper --- .../src/connectors/connectVoiceSearch.js | 27 +++- .../src/lib/voiceSearchHelper/index.ts | 127 ++++++++++++++++++ .../src/components/VoiceSearch.tsx | 10 +- 3 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts diff --git a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.js b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.js index 309c15184f..4a24dfba7f 100644 --- a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.js +++ b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.js @@ -1,9 +1,24 @@ import createConnector from '../core/createConnector'; +import voiceSearchHelper from '../lib/voiceSearchHelper'; -export default createConnector({ - displayName: 'AlgoliaVoiceSearch', +export default Composed => { + const helper = voiceSearchHelper({}); - getProvidedProps() { - return {}; - }, -}); + const { getState, isBrowserSupported, isListening, toggleListening } = helper; + + const connector = createConnector({ + displayName: 'AlgoliaVoiceSearch', + + getProvidedProps() { + return { + isBrowserSupported, + isListening, + toggleListening, + voiceListeningState: getState(), + searchAsYouSpeak: undefined, + }; + }, + }); + + return connector(Composed); +}; diff --git a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts new file mode 100644 index 0000000000..2a262c19ae --- /dev/null +++ b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts @@ -0,0 +1,127 @@ +const STATUS_INITIAL = 'initial'; +const STATUS_ASKING_PERMISSION = 'askingPermission'; +const STATUS_WAITING = 'waiting'; +const STATUS_RECOGNIZING = 'recognizing'; +const STATUS_FINISHED = 'finished'; +const STATUS_ERROR = 'error'; + +export type VoiceSearchHelperParams = { + searchAsYouSpeak: boolean; + onQueryChange: (query: string) => void; + onStateChange: () => void; +}; + +export type VoiceListeningState = { + status: string; + transcript?: string; + isSpeechFinal?: boolean; + errorCode?: string; +}; + +export type ToggleListening = () => void; + +interface SpeechRecognitionAPI { + new (): SpeechRecognition; +} + +export default function voiceSearchHelper({ + searchAsYouSpeak, + onQueryChange, + onStateChange, +}: VoiceSearchHelperParams) { + const SpeechRecognitionAPI: SpeechRecognitionAPI = + (window as any).webkitSpeechRecognition || + (window as any).SpeechRecognition; + const getDefaultState = (status: string): VoiceListeningState => ({ + status, + transcript: undefined, + isSpeechFinal: undefined, + errorCode: undefined, + }); + let state: VoiceListeningState = getDefaultState(STATUS_INITIAL); + let recognition: SpeechRecognition | undefined; + + const isBrowserSupported = () => Boolean(SpeechRecognitionAPI); + + const isListening = () => + state.status === STATUS_ASKING_PERMISSION || + state.status === STATUS_WAITING || + state.status === STATUS_RECOGNIZING; + + const setState = (newState = {}) => { + state = { ...state, ...newState }; + onStateChange(); + }; + + const getState = (): VoiceListeningState => state; + + const resetState = (status = STATUS_INITIAL) => { + setState(getDefaultState(status)); + }; + + const stop = () => { + if (recognition) { + recognition.stop(); + recognition = undefined; + } + resetState(); + }; + + const start = () => { + recognition = new SpeechRecognitionAPI(); + if (!recognition) { + return; + } + resetState(STATUS_ASKING_PERMISSION); + recognition.interimResults = true; + recognition.onstart = () => { + setState({ + status: STATUS_WAITING, + }); + }; + recognition.onerror = (event: SpeechRecognitionError) => { + setState({ status: STATUS_ERROR, errorCode: event.error }); + }; + recognition.onresult = (event: SpeechRecognitionEvent) => { + setState({ + status: STATUS_RECOGNIZING, + transcript: + event.results[0] && + event.results[0][0] && + event.results[0][0].transcript, + isSpeechFinal: event.results[0] && event.results[0].isFinal, + }); + if (searchAsYouSpeak && state.transcript) { + onQueryChange(state.transcript); + } + }; + recognition.onend = () => { + if (!state.errorCode && state.transcript && !searchAsYouSpeak) { + onQueryChange(state.transcript); + } + if (state.status !== STATUS_ERROR) { + setState({ status: STATUS_FINISHED }); + } + }; + + recognition.start(); + }; + + const toggleListening = () => { + if (!isBrowserSupported()) { + return; + } + if (isListening()) { + stop(); + } else { + start(); + } + }; + + return { + getState, + isBrowserSupported, + isListening, + toggleListening, + }; +} diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 43bd9cb061..63d599af60 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -20,11 +20,11 @@ type InnerComponentProps = { isListening: boolean; transcript?: string; isSpeechFinal?: boolean; - isSupportedBrowser: boolean; + isBrowserSupported: boolean; }; type VoiceSearchProps = { - isSupportedBrowser: boolean; + isBrowserSupported: boolean; isListening: boolean; toggleListening: ToggleListening; voiceListeningState: VoiceListeningState; @@ -110,7 +110,7 @@ const DefaultStatusComponent: React.FunctionComponent = ({ const VoiceSearch = ({ translate, - isSupportedBrowser, + isBrowserSupported, isListening, toggleListening, voiceListeningState, @@ -132,7 +132,7 @@ const VoiceSearch = ({ isListening, transcript, isSpeechFinal, - isSupportedBrowser, + isBrowserSupported, }; return ( @@ -142,7 +142,7 @@ const VoiceSearch = ({ type="button" title={translate('buttonTitle')} onClick={onClick} - disabled={!isSupportedBrowser} + disabled={!isBrowserSupported} > From f24ee7487ca9c8d94c4b8e43dd012206dcf7ad73 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Fri, 12 Apr 2019 17:04:11 +0200 Subject: [PATCH 03/66] feat(voiceSearch): WIP adding connector --- package.json | 2 +- .../src/connectors/connectVoiceSearch.js | 24 ------- .../src/connectors/connectVoiceSearch.ts | 72 +++++++++++++++++++ .../src/lib/voiceSearchHelper/index.ts | 32 +++++---- .../src/components/VoiceSearch.tsx | 18 ++--- yarn.lock | 8 +-- 6 files changed, 103 insertions(+), 53 deletions(-) delete mode 100644 packages/react-instantsearch-core/src/connectors/connectVoiceSearch.js create mode 100644 packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts diff --git a/package.json b/package.json index 682d50eae6..0cafe8c8d2 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "eslint-plugin-react": "7.12.4", "glob": "7.1.3", "html-webpack-plugin": "3.2.0", - "instantsearch.css": "7.1.1", + "instantsearch.css": "7.3.1", "jest": "24.5.0", "jest-watch-typeahead": "0.2.1", "json": "9.0.6", diff --git a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.js b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.js deleted file mode 100644 index 4a24dfba7f..0000000000 --- a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.js +++ /dev/null @@ -1,24 +0,0 @@ -import createConnector from '../core/createConnector'; -import voiceSearchHelper from '../lib/voiceSearchHelper'; - -export default Composed => { - const helper = voiceSearchHelper({}); - - const { getState, isBrowserSupported, isListening, toggleListening } = helper; - - const connector = createConnector({ - displayName: 'AlgoliaVoiceSearch', - - getProvidedProps() { - return { - isBrowserSupported, - isListening, - toggleListening, - voiceListeningState: getState(), - searchAsYouSpeak: undefined, - }; - }, - }); - - return connector(Composed); -}; diff --git a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts new file mode 100644 index 0000000000..eef0671aef --- /dev/null +++ b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts @@ -0,0 +1,72 @@ +import createConnector from '../core/createConnector'; +import voiceSearchHelper from '../lib/voiceSearchHelper'; +import { refineValue, getCurrentRefinementValue } from '../core/indexUtils'; + +function getId() { + return 'query'; +} + +function refine(props, searchState, nextRefinement, context) { + const id = getId(); + const nextValue = { [id]: nextRefinement }; + const resetPage = true; + return refineValue(searchState, nextValue, context, resetPage); +} + +function getCurrentRefinement(props, searchState, context) { + const id = getId(); + const refinementsCallback = currentRefinement => currentRefinement || ''; + return getCurrentRefinementValue( + props, + searchState, + context, + id, + '', + refinementsCallback + ); +} + +export default Composed => { + const helper = voiceSearchHelper(); + + const connector = createConnector({ + displayName: 'AlgoliaVoiceSearch', + + getProvidedProps({ searchAsYouSpeak = false } = {}) { + helper.setSearchAsYouSpeak(searchAsYouSpeak); + helper.setOnQueryChange(query => { + this.refine(query); + }); + helper.setOnStateChange(() => { + this.context.ais.widgetsManager.update(); + }); + + const { + getState, + isBrowserSupported, + isListening, + toggleListening, + } = helper; + + return { + isBrowserSupported, + isListening, + toggleListening, + voiceListeningState: getState(), + searchAsYouSpeak, + }; + }, + + refine(props, searchState, nextRefinement) { + return refine(props, searchState, nextRefinement, this.context); + }, + + getSearchParameters(searchParameters, props, searchState) { + return searchParameters.setQuery( + getCurrentRefinement(props, searchState, this.context) + ); + }, + }); + + return connector(Composed); +}; diff --git a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts index 2a262c19ae..1061c14522 100644 --- a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts @@ -5,11 +5,8 @@ const STATUS_RECOGNIZING = 'recognizing'; const STATUS_FINISHED = 'finished'; const STATUS_ERROR = 'error'; -export type VoiceSearchHelperParams = { - searchAsYouSpeak: boolean; - onQueryChange: (query: string) => void; - onStateChange: () => void; -}; +type QueryChangeListener = (query: string) => void; +type StateChangeListener = () => void; export type VoiceListeningState = { status: string; @@ -20,16 +17,8 @@ export type VoiceListeningState = { export type ToggleListening = () => void; -interface SpeechRecognitionAPI { - new (): SpeechRecognition; -} - -export default function voiceSearchHelper({ - searchAsYouSpeak, - onQueryChange, - onStateChange, -}: VoiceSearchHelperParams) { - const SpeechRecognitionAPI: SpeechRecognitionAPI = +export default function voiceSearchHelper() { + const SpeechRecognitionAPI: new () => SpeechRecognition = (window as any).webkitSpeechRecognition || (window as any).SpeechRecognition; const getDefaultState = (status: string): VoiceListeningState => ({ @@ -40,6 +29,16 @@ export default function voiceSearchHelper({ }); let state: VoiceListeningState = getDefaultState(STATUS_INITIAL); let recognition: SpeechRecognition | undefined; + let searchAsYouSpeak; + let onQueryChange: QueryChangeListener = () => {}; + let onStateChange: StateChangeListener = () => {}; + + const setOnQueryChange = (queryChangeListener: QueryChangeListener) => + (onQueryChange = queryChangeListener); + const setOnStateChange = (stateChangeListener: StateChangeListener) => + (onStateChange = stateChangeListener); + + const setSearchAsYouSpeak = value => (searchAsYouSpeak = value); const isBrowserSupported = () => Boolean(SpeechRecognitionAPI); @@ -119,6 +118,9 @@ export default function voiceSearchHelper({ }; return { + setOnQueryChange, + setOnStateChange, + setSearchAsYouSpeak, getState, isBrowserSupported, isListening, diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 63d599af60..2be0ae7912 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -49,9 +49,9 @@ const DefaultButtonComponent: React.FunctionComponent = ({ viewBox="0 0 24 24" fill="none" stroke="currentColor" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" > @@ -69,9 +69,9 @@ const DefaultButtonComponent: React.FunctionComponent = ({ viewBox="0 0 24 24" fill="none" stroke="currentColor" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" > = ({ viewBox="0 0 24 24" fill="none" stroke="currentColor" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" > diff --git a/yarn.lock b/yarn.lock index 776315016f..2022fe8034 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8966,10 +8966,10 @@ insert-css@^2.0.0: resolved "https://registry.yarnpkg.com/insert-css/-/insert-css-2.0.0.tgz#eb5d1097b7542f4c79ea3060d3aee07d053880f4" integrity sha1-610Ql7dUL0x56jBg067gfQU4gPQ= -instantsearch.css@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/instantsearch.css/-/instantsearch.css-7.1.1.tgz#64fa9f07ff39ab088b00cd3f0fa3ea697d2215f7" - integrity sha512-8+2FLbZk9uK+VRFJ4tXByMNX9KTsPOrY/nkw4oe+8HH+jcHoVMQkUdxbFu7mSUHUdZjh5kK9Xk8IQau1Sy+VKw== +instantsearch.css@7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/instantsearch.css/-/instantsearch.css-7.3.1.tgz#7ab74a8f355091ae040947a9cf5438f379026622" + integrity sha512-/kaMDna5D+Q9mImNBHEhb9HgHATDOFKYii7N1Iwvrj+lmD9gBJLqVhUw67gftq2O0QI330pFza+CRscIwB1wQQ== interpret@^1.0.0: version "1.1.0" From d23b3d5b70e64c9d0c69e800e85bf4443c34f5dd Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Mon, 15 Apr 2019 13:09:12 +0200 Subject: [PATCH 04/66] Update packages/react-instantsearch-dom/src/components/VoiceSearch.tsx Co-Authored-By: eunjae-lee --- packages/react-instantsearch-dom/src/components/VoiceSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 2be0ae7912..fe2e3236f9 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -104,7 +104,7 @@ const DefaultButtonComponent: React.FunctionComponent = ({ } }; -const DefaultStatusComponent: React.FunctionComponent = ({ +const DefaultStatusComponent: React.FC = ({ status, }) =>

{status}

; From d7eb9ed489dca01b7afed2c24c79549ddb6afc18 Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Mon, 15 Apr 2019 13:09:55 +0200 Subject: [PATCH 05/66] Update packages/react-instantsearch-dom/src/components/VoiceSearch.tsx Co-Authored-By: eunjae-lee --- packages/react-instantsearch-dom/src/components/VoiceSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index fe2e3236f9..46e6019197 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -35,7 +35,7 @@ type VoiceSearchProps = { statusComponent?: React.FunctionComponent; }; -const DefaultButtonComponent: React.FunctionComponent = ({ +const DefaultButton: React.FunctionComponent = ({ status, errorCode, isListening, From 1ccc9e24c0e606f09292593a295b3880fab9e31e Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Mon, 15 Apr 2019 13:10:02 +0200 Subject: [PATCH 06/66] Update packages/react-instantsearch-dom/src/components/VoiceSearch.tsx Co-Authored-By: eunjae-lee --- packages/react-instantsearch-dom/src/components/VoiceSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 46e6019197..8e217c572f 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -144,7 +144,7 @@ const VoiceSearch = ({ onClick={onClick} disabled={!isBrowserSupported} > - +
From aebd6e26fa72ba33ec143cc7a34385a140e91fc5 Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Mon, 15 Apr 2019 13:10:08 +0200 Subject: [PATCH 07/66] Update packages/react-instantsearch-dom/src/components/VoiceSearch.tsx Co-Authored-By: eunjae-lee --- packages/react-instantsearch-dom/src/components/VoiceSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 8e217c572f..f59c3444bc 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -116,7 +116,7 @@ const VoiceSearch = ({ voiceListeningState, searchAsYouSpeak = false, - buttonComponent: ButtonComponent = DefaultButtonComponent, + buttonComponent: Button = DefaultButtonComponent, statusComponent: StatusComponent = DefaultStatusComponent, }: VoiceSearchProps) => { const onClick = (event: React.MouseEvent) => { From aff57a8a447731473ea4a70be0a1e2c101be8021 Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Mon, 15 Apr 2019 13:10:13 +0200 Subject: [PATCH 08/66] Update packages/react-instantsearch-dom/src/components/VoiceSearch.tsx Co-Authored-By: eunjae-lee --- packages/react-instantsearch-dom/src/components/VoiceSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index f59c3444bc..ab9f85f917 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -147,7 +147,7 @@ const VoiceSearch = ({
- +
); From 1bd9f5c7095eb273fd2e42c622756a20e61b9024 Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Mon, 15 Apr 2019 13:10:49 +0200 Subject: [PATCH 09/66] Apply suggestions from code review Co-Authored-By: eunjae-lee --- packages/react-instantsearch-dom/src/components/VoiceSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index ab9f85f917..1323335b3f 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -117,7 +117,7 @@ const VoiceSearch = ({ searchAsYouSpeak = false, buttonComponent: Button = DefaultButtonComponent, - statusComponent: StatusComponent = DefaultStatusComponent, + statusComponent: Status = DefaultStatusComponent, }: VoiceSearchProps) => { const onClick = (event: React.MouseEvent) => { event.currentTarget.blur(); From 9bd9c681939b9646e626d8d3c789a4389fa71d67 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 15 Apr 2019 13:18:06 +0200 Subject: [PATCH 10/66] feat(voiceSearch): rename variables --- .../src/components/VoiceSearch.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 1323335b3f..6b532a7648 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -31,11 +31,11 @@ type VoiceSearchProps = { searchAsYouSpeak?: boolean; translate: Translate; - buttonComponent?: React.FunctionComponent; - statusComponent?: React.FunctionComponent; + buttonComponent?: React.FC; + statusComponent?: React.FC; }; -const DefaultButton: React.FunctionComponent = ({ +const DefaultButton: React.FC = ({ status, errorCode, isListening, @@ -104,9 +104,9 @@ const DefaultButton: React.FunctionComponent = ({ } }; -const DefaultStatusComponent: React.FC = ({ - status, -}) =>

{status}

; +const DefaultStatus: React.FC = ({ status }) => ( +

{status}

+); const VoiceSearch = ({ translate, @@ -116,8 +116,8 @@ const VoiceSearch = ({ voiceListeningState, searchAsYouSpeak = false, - buttonComponent: Button = DefaultButtonComponent, - statusComponent: Status = DefaultStatusComponent, + buttonComponent: Button = DefaultButton, + statusComponent: Status = DefaultStatus, }: VoiceSearchProps) => { const onClick = (event: React.MouseEvent) => { event.currentTarget.blur(); From ec2f150e7ab0edda4398d430d37646ec404aa9c3 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 15 Apr 2019 13:20:27 +0200 Subject: [PATCH 11/66] feat(voiceSearch): rename helper to voiceSearch --- .../src/connectors/connectVoiceSearch.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts index eef0671aef..1f509e2220 100644 --- a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts +++ b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts @@ -27,17 +27,17 @@ function getCurrentRefinement(props, searchState, context) { } export default Composed => { - const helper = voiceSearchHelper(); + const voiceSearch = voiceSearchHelper(); const connector = createConnector({ displayName: 'AlgoliaVoiceSearch', getProvidedProps({ searchAsYouSpeak = false } = {}) { - helper.setSearchAsYouSpeak(searchAsYouSpeak); - helper.setOnQueryChange(query => { + voiceSearch.setSearchAsYouSpeak(searchAsYouSpeak); + voiceSearch.setOnQueryChange(query => { this.refine(query); }); - helper.setOnStateChange(() => { + voiceSearch.setOnStateChange(() => { this.context.ais.widgetsManager.update(); }); @@ -46,7 +46,7 @@ export default Composed => { isBrowserSupported, isListening, toggleListening, - } = helper; + } = voiceSearch; return { isBrowserSupported, From b3b6093663e2cb11a3e4c2310c02dae126950fbb Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 15 Apr 2019 13:26:34 +0200 Subject: [PATCH 12/66] type(voiceSearch): fix lint error --- .../src/connectors/connectVoiceSearch.ts | 2 +- .../react-instantsearch-core/src/lib/voiceSearchHelper/index.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts index 1f509e2220..4d70c5d421 100644 --- a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts +++ b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts @@ -6,7 +6,7 @@ function getId() { return 'query'; } -function refine(props, searchState, nextRefinement, context) { +function refine(_props, searchState, nextRefinement, context) { const id = getId(); const nextValue = { [id]: nextRefinement }; const resetPage = true; diff --git a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts index 1061c14522..a0f60bfbc4 100644 --- a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts @@ -30,7 +30,9 @@ export default function voiceSearchHelper() { let state: VoiceListeningState = getDefaultState(STATUS_INITIAL); let recognition: SpeechRecognition | undefined; let searchAsYouSpeak; + // tslint:disable-next-line:no-empty let onQueryChange: QueryChangeListener = () => {}; + // tslint:disable-next-line:no-empty let onStateChange: StateChangeListener = () => {}; const setOnQueryChange = (queryChangeListener: QueryChangeListener) => From 7e8d3c3646abc19215da008b47a2af280ade3aad Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 15 Apr 2019 16:54:08 +0200 Subject: [PATCH 13/66] test(voiceSearch): add tests for component --- .../src/components/__tests__/VoiceSearch.tsx | 117 ++++++++++++ .../__snapshots__/VoiceSearch.tsx.snap | 175 ++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx create mode 100644 packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap diff --git a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx new file mode 100644 index 0000000000..d84924d04f --- /dev/null +++ b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import Enzyme, { mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import VoiceSearch from '../VoiceSearch'; + +Enzyme.configure({ adapter: new Adapter() }); + +const renderAndMatchSnapshot = (element: JSX.Element) => { + const instance = renderer.create(element); + expect(instance.toJSON()).toMatchSnapshot(); + instance.unmount(); +}; + +const defaultProps = { + isBrowserSupported: true, + isListening: false, + toggleListening: () => {}, + voiceListeningState: { + status: 'initial', + transcript: undefined, + isSpeechFinal: undefined, + errorCode: undefined, + }, +}; + +describe('VoiceSearch', () => { + it('does nothing', () => { + expect(true).toBe(true); + }); + + describe('button', () => { + it('calls toggleListening when button is clicked', () => { + const props = { + ...defaultProps, + toggleListening: jest.fn(), + }; + const wrapper = mount(); + wrapper.find('button').simulate('click'); + expect(props.toggleListening).toHaveBeenCalledTimes(1); + }); + }); + + describe('Rendering', () => { + it('with default props', () => { + renderAndMatchSnapshot(); + }); + + it('with custom component for button with isListening: false', () => { + const customButton = ({ isListening }) => ( + + ); + + renderAndMatchSnapshot( + + ); + }); + + it('with custom component for button with isListening: true', () => { + const customButton = ({ isListening }) => ( + + ); + + const props = { + isBrowserSupported: true, + isListening: true, + toggleListening: () => {}, + voiceListeningState: { + status: 'recognizing', + transcript: undefined, + isSpeechFinal: undefined, + errorCode: undefined, + }, + }; + + renderAndMatchSnapshot( + + ); + }); + + it('with custom template for status', () => { + const customStatus = ({ + status, + errorCode, + isListening, + transcript, + isSpeechFinal, + isBrowserSupported, + }) => ( +
+

status: {status}

+

errorCode: {errorCode}

+

isListening: {isListening}

+

transcript: {transcript}

+

isSpeechFinal: {isSpeechFinal}

+

isBrowserSupported: {isBrowserSupported}

+
+ ); + + const props = { + isBrowserSupported: true, + isListening: true, + toggleListening: () => {}, + voiceListeningState: { + status: 'recognizing', + transcript: 'Hello', + isSpeechFinal: false, + errorCode: undefined, + }, + }; + + renderAndMatchSnapshot( + + ); + }); + }); +}); diff --git a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap new file mode 100644 index 0000000000..132aaa9698 --- /dev/null +++ b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap @@ -0,0 +1,175 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`VoiceSearch Rendering with custom component for button with isListening: false 1`] = ` +
+ + +
+

+ initial +

+
+
+`; + +exports[`VoiceSearch Rendering with custom component for button with isListening: true 1`] = ` +
+ + +
+

+ recognizing +

+
+
+`; + +exports[`VoiceSearch Rendering with custom template for status 1`] = ` +
+ +
+
+

+ status: + recognizing +

+

+ errorCode: +

+

+ isListening: +

+

+ transcript: + Hello +

+

+ isSpeechFinal: +

+

+ isBrowserSupported: +

+
+
+
+`; + +exports[`VoiceSearch Rendering with default props 1`] = ` +
+ +
+

+ initial +

+
+
+`; From b2bd80d4cafe6bc9e93e0f7859eafefe6c39e911 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 15 Apr 2019 17:32:07 +0200 Subject: [PATCH 14/66] chore(voiceSearch): fix lint error --- .../src/components/__tests__/VoiceSearch.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx index d84924d04f..e2e13a8f26 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx @@ -15,6 +15,7 @@ const renderAndMatchSnapshot = (element: JSX.Element) => { const defaultProps = { isBrowserSupported: true, isListening: false, + // tslint:disable-next-line:no-empty toggleListening: () => {}, voiceListeningState: { status: 'initial', @@ -62,9 +63,8 @@ describe('VoiceSearch', () => { ); const props = { - isBrowserSupported: true, + ...defaultProps, isListening: true, - toggleListening: () => {}, voiceListeningState: { status: 'recognizing', transcript: undefined, @@ -98,9 +98,8 @@ describe('VoiceSearch', () => { ); const props = { - isBrowserSupported: true, + ...defaultProps, isListening: true, - toggleListening: () => {}, voiceListeningState: { status: 'recognizing', transcript: 'Hello', From b3118ecc5893f5cd936be0575aa3042d6c904a21 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 15 Apr 2019 17:38:52 +0200 Subject: [PATCH 15/66] chore(voiceSearch): disable tslint for unused parameter --- .../src/connectors/connectVoiceSearch.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts index 4d70c5d421..dd35a73fad 100644 --- a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts +++ b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts @@ -6,7 +6,9 @@ function getId() { return 'query'; } +/* tslint:disable:variable-name */ function refine(_props, searchState, nextRefinement, context) { + /* tslint:enable:variable-name */ const id = getId(); const nextValue = { [id]: nextRefinement }; const resetPage = true; From e1f73fa49e82a9794877539f5c5e4cfbcf193edd Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 15 Apr 2019 17:59:04 +0200 Subject: [PATCH 16/66] test(voiceSearch): add stories --- stories/VoiceSearch.stories.js | 55 +++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/stories/VoiceSearch.stories.js b/stories/VoiceSearch.stories.js index 6def88d449..b55b062d13 100644 --- a/stories/VoiceSearch.stories.js +++ b/stories/VoiceSearch.stories.js @@ -1,16 +1,51 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; -import { VoiceSearch } from 'react-instantsearch-dom'; +import { VoiceSearch, SearchBox } from 'react-instantsearch-dom'; import { WrapWithHits } from './util'; const stories = storiesOf('VoiceSearch', module); -stories.add('default', () => ( - - - -)); +stories + .add('default', () => ( + +

+ To see this button disabled, test it on unsupported browsers like + Safari, Firefox, etc. +

+ +
+ )) + .add('without status', () => ( + + null} /> + + )) + .add('with a SearchBox', () => ( + + + + + )) + .add('with a custom button text', () => ( + + (isListening ? 'Stop' : 'Start')} + /> + + )); From 761a01b020529a471223567148a92e723e858768 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 15 Apr 2019 18:01:58 +0200 Subject: [PATCH 17/66] fix(voiceSearch): fix wrong props --- .../src/connectors/connectVoiceSearch.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts index dd35a73fad..21ab791e79 100644 --- a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts +++ b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts @@ -51,8 +51,8 @@ export default Composed => { } = voiceSearch; return { - isBrowserSupported, - isListening, + isBrowserSupported: isBrowserSupported(), + isListening: isListening(), toggleListening, voiceListeningState: getState(), searchAsYouSpeak, From 3c9700e1287bd187497292b6d34f3becacf98e2d Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 16 Apr 2019 10:20:15 +0200 Subject: [PATCH 18/66] chore(voiceSearch): fix lint error --- .../src/connectors/connectVoiceSearch.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts index 21ab791e79..1124cad27d 100644 --- a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts +++ b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts @@ -6,9 +6,7 @@ function getId() { return 'query'; } -/* tslint:disable:variable-name */ -function refine(_props, searchState, nextRefinement, context) { - /* tslint:enable:variable-name */ +function refine(searchState, nextRefinement, context) { const id = getId(); const nextValue = { [id]: nextRefinement }; const resetPage = true; @@ -59,8 +57,8 @@ export default Composed => { }; }, - refine(props, searchState, nextRefinement) { - return refine(props, searchState, nextRefinement, this.context); + refine(_0, searchState, nextRefinement) { + return refine(searchState, nextRefinement, this.context); }, getSearchParameters(searchParameters, props, searchState) { From ed3e6ccb92b96d05b46cd0aec02816d4f1689103 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 16 Apr 2019 10:54:18 +0200 Subject: [PATCH 19/66] test(voiceSearch): add story for custom button --- stories/VoiceSearch.stories.js | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/stories/VoiceSearch.stories.js b/stories/VoiceSearch.stories.js index b55b062d13..e1796b8728 100644 --- a/stories/VoiceSearch.stories.js +++ b/stories/VoiceSearch.stories.js @@ -38,14 +38,25 @@ stories )) - .add('with a custom button text', () => ( - - (isListening ? 'Stop' : 'Start')} - /> - - )); + .add('with a custom button text', () => { + const style = window.document.createElement('style'); + window.document.head.appendChild(style); + [ + `.custom-button-story .ais-VoiceSearch-button:hover { + background: inherit; + }`, + ].forEach(rule => style.sheet.insertRule(rule)); + return ( +
+ + (isListening ? '⏹' : '🎙')} + /> + +
+ ); + }); From 30e1292eced4fbfee01a83b7f5c9b7dcfda08d42 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 16 Apr 2019 12:46:29 +0200 Subject: [PATCH 20/66] chore(voiceSearch): change to curly brace --- .../src/lib/voiceSearchHelper/index.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts index a0f60bfbc4..bf79a0b8ed 100644 --- a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts @@ -35,12 +35,15 @@ export default function voiceSearchHelper() { // tslint:disable-next-line:no-empty let onStateChange: StateChangeListener = () => {}; - const setOnQueryChange = (queryChangeListener: QueryChangeListener) => - (onQueryChange = queryChangeListener); - const setOnStateChange = (stateChangeListener: StateChangeListener) => - (onStateChange = stateChangeListener); - - const setSearchAsYouSpeak = value => (searchAsYouSpeak = value); + const setOnQueryChange = (queryChangeListener: QueryChangeListener) => { + onQueryChange = queryChangeListener; + }; + const setOnStateChange = (stateChangeListener: StateChangeListener) => { + onStateChange = stateChangeListener; + }; + const setSearchAsYouSpeak = value => { + searchAsYouSpeak = value; + }; const isBrowserSupported = () => Boolean(SpeechRecognitionAPI); From 78a77bafbd12bc3916e55143c40a29bc566b69b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 16 Apr 2019 12:48:29 +0200 Subject: [PATCH 21/66] Apply suggestions from code review Co-Authored-By: eunjae-lee --- .../react-instantsearch-core/src/lib/voiceSearchHelper/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts index bf79a0b8ed..66a3953753 100644 --- a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts @@ -29,7 +29,7 @@ export default function voiceSearchHelper() { }); let state: VoiceListeningState = getDefaultState(STATUS_INITIAL); let recognition: SpeechRecognition | undefined; - let searchAsYouSpeak; + let searchAsYouSpeak: boolean; // tslint:disable-next-line:no-empty let onQueryChange: QueryChangeListener = () => {}; // tslint:disable-next-line:no-empty From 91260ad3403b47b1a1b1ec6d6828ed0cac85a3ab Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 16 Apr 2019 13:06:56 +0200 Subject: [PATCH 22/66] chore(voiceSearch): remove comments since the config is now global --- .../react-instantsearch-core/src/lib/voiceSearchHelper/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts index 66a3953753..6a334544ee 100644 --- a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts @@ -30,9 +30,7 @@ export default function voiceSearchHelper() { let state: VoiceListeningState = getDefaultState(STATUS_INITIAL); let recognition: SpeechRecognition | undefined; let searchAsYouSpeak: boolean; - // tslint:disable-next-line:no-empty let onQueryChange: QueryChangeListener = () => {}; - // tslint:disable-next-line:no-empty let onStateChange: StateChangeListener = () => {}; const setOnQueryChange = (queryChangeListener: QueryChangeListener) => { From b53ed877ab1034765d6f80419daf2a6c57230768 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 16 Apr 2019 15:50:49 +0200 Subject: [PATCH 23/66] chore(voiceSearch): add default value to searchAsYouSpeak and put typing for setSearchAsYouSpeak --- .../src/lib/voiceSearchHelper/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts index 6a334544ee..1ebfa5d5b8 100644 --- a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts @@ -29,7 +29,7 @@ export default function voiceSearchHelper() { }); let state: VoiceListeningState = getDefaultState(STATUS_INITIAL); let recognition: SpeechRecognition | undefined; - let searchAsYouSpeak: boolean; + let searchAsYouSpeak: boolean = false; let onQueryChange: QueryChangeListener = () => {}; let onStateChange: StateChangeListener = () => {}; @@ -39,7 +39,7 @@ export default function voiceSearchHelper() { const setOnStateChange = (stateChangeListener: StateChangeListener) => { onStateChange = stateChangeListener; }; - const setSearchAsYouSpeak = value => { + const setSearchAsYouSpeak = (value: boolean) => { searchAsYouSpeak = value; }; From 4481bd459956585fbba0b29f09c80a5345cf0ab4 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 16 Apr 2019 15:57:53 +0200 Subject: [PATCH 24/66] chore(voiceSearch): clean up svg --- .../src/components/VoiceSearch.tsx | 83 ++++++++----------- 1 file changed, 33 insertions(+), 50 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 6b532a7648..d853ec7035 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -35,75 +35,58 @@ type VoiceSearchProps = { statusComponent?: React.FC; }; -const DefaultButton: React.FC = ({ - status, - errorCode, - isListening, -}) => { +const getDefaultButtonInnerElement = ( + status: string, + errorCode: string | undefined, + isListening: boolean +) => { if (status === 'error' && errorCode === 'not-allowed') { return ( - + <> - + ); - } else if (isListening) { + } else { return ( - + <> - - ); - } else { - return ( - - - - - - + ); } }; +const DefaultButton: React.FC = ({ + status, + errorCode, + isListening, +}) => { + return ( + + ${getDefaultButtonInnerElement(status, errorCode, isListening)} + + ); +}; + const DefaultStatus: React.FC = ({ status }) => (

{status}

); From 5a2b9ae07134353f4f07001689c30c58f4f1d6c4 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 16 Apr 2019 16:00:12 +0200 Subject: [PATCH 25/66] fix(voiceSearch): fix class name for status --- packages/react-instantsearch-dom/src/components/VoiceSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index d853ec7035..64e18a54ad 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -129,7 +129,7 @@ const VoiceSearch = ({ > -
+
From b4067cad9f0d80b05dfe4ddefa307bbc70164ae6 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 16 Apr 2019 16:00:26 +0200 Subject: [PATCH 26/66] chore(voiceSearch): remove unnecessary comment --- .../src/components/__tests__/VoiceSearch.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx index e2e13a8f26..904e95b98d 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx @@ -15,7 +15,6 @@ const renderAndMatchSnapshot = (element: JSX.Element) => { const defaultProps = { isBrowserSupported: true, isListening: false, - // tslint:disable-next-line:no-empty toggleListening: () => {}, voiceListeningState: { status: 'initial', From c28a6bcc86e64af1bfc2f266a10bbc178c27122c Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 16 Apr 2019 16:01:50 +0200 Subject: [PATCH 27/66] test(voiceSearch): remove unused test case --- .../src/components/__tests__/VoiceSearch.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx index 904e95b98d..6f91431033 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx @@ -25,10 +25,6 @@ const defaultProps = { }; describe('VoiceSearch', () => { - it('does nothing', () => { - expect(true).toBe(true); - }); - describe('button', () => { it('calls toggleListening when button is clicked', () => { const props = { From a4b3aaca26b680df337b6e9ba5deb3210b1702f8 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 16 Apr 2019 17:00:53 +0200 Subject: [PATCH 28/66] test(voiceSearch): change way to match snapshot --- .../src/components/__tests__/VoiceSearch.tsx | 21 +- .../__snapshots__/VoiceSearch.tsx.snap | 221 +++++------------- 2 files changed, 68 insertions(+), 174 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx index 6f91431033..a3e537c8e5 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx @@ -1,17 +1,10 @@ import React from 'react'; -import renderer from 'react-test-renderer'; -import Enzyme, { mount } from 'enzyme'; +import Enzyme, { mount, shallow } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import VoiceSearch from '../VoiceSearch'; Enzyme.configure({ adapter: new Adapter() }); -const renderAndMatchSnapshot = (element: JSX.Element) => { - const instance = renderer.create(element); - expect(instance.toJSON()).toMatchSnapshot(); - instance.unmount(); -}; - const defaultProps = { isBrowserSupported: true, isListening: false, @@ -39,7 +32,8 @@ describe('VoiceSearch', () => { describe('Rendering', () => { it('with default props', () => { - renderAndMatchSnapshot(); + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); }); it('with custom component for button with isListening: false', () => { @@ -47,9 +41,10 @@ describe('VoiceSearch', () => { ); - renderAndMatchSnapshot( + const wrapper = shallow( ); + expect(wrapper).toMatchSnapshot(); }); it('with custom component for button with isListening: true', () => { @@ -68,9 +63,10 @@ describe('VoiceSearch', () => { }, }; - renderAndMatchSnapshot( + const wrapper = shallow( ); + expect(wrapper).toMatchSnapshot(); }); it('with custom template for status', () => { @@ -103,9 +99,10 @@ describe('VoiceSearch', () => { }, }; - renderAndMatchSnapshot( + const wrapper = shallow( ); + expect(wrapper).toMatchSnapshot(); }); }); }); diff --git a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap index 132aaa9698..fb1a66f270 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap +++ b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap @@ -1,175 +1,72 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`VoiceSearch Rendering with custom component for button with isListening: false 1`] = ` -
- - -
-

- initial -

-
-
+ `; exports[`VoiceSearch Rendering with custom component for button with isListening: true 1`] = ` -
- - -
-

- recognizing -

-
-
+ `; exports[`VoiceSearch Rendering with custom template for status 1`] = ` -
- -
-
-

- status: - recognizing -

-

- errorCode: -

-

- isListening: -

-

- transcript: - Hello -

-

- isSpeechFinal: -

-

- isBrowserSupported: -

-
-
-
+ `; exports[`VoiceSearch Rendering with default props 1`] = ` -
- -
-

- initial -

-
-
+ `; From bb193c52d105d03e87e50af929ea8128b42bc16b Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Wed, 17 Apr 2019 15:39:30 +0200 Subject: [PATCH 29/66] chore(voiceSearch): remove 'else' --- .../src/components/VoiceSearch.tsx | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 64e18a54ad..56dc819525 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -50,19 +50,18 @@ const getDefaultButtonInnerElement = ( ); - } else { - return ( - <> - - - - - - ); } + return ( + <> + + + + + + ); }; const DefaultButton: React.FC = ({ From b04cf896230bc9b0b3d8be2a92fdd098b13625b7 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Wed, 17 Apr 2019 15:39:55 +0200 Subject: [PATCH 30/66] chore(voiceSearch): remove unnecessary $ --- packages/react-instantsearch-dom/src/components/VoiceSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 56dc819525..9b17c79d1e 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -81,7 +81,7 @@ const DefaultButton: React.FC = ({ strokeLinecap="round" strokeLinejoin="round" > - ${getDefaultButtonInnerElement(status, errorCode, isListening)} + {getDefaultButtonInnerElement(status, errorCode, isListening)} ); }; From e7c1119056c1ef3bd8c5b82903e66a492229ae58 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Wed, 17 Apr 2019 16:42:51 +0200 Subject: [PATCH 31/66] test(voiceSearch): add stories --- stories/VoiceSearch.stories.js | 62 ----------- stories/VoiceSearch.stories.tsx | 175 ++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 62 deletions(-) delete mode 100644 stories/VoiceSearch.stories.js create mode 100644 stories/VoiceSearch.stories.tsx diff --git a/stories/VoiceSearch.stories.js b/stories/VoiceSearch.stories.js deleted file mode 100644 index e1796b8728..0000000000 --- a/stories/VoiceSearch.stories.js +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { storiesOf } from '@storybook/react'; -import { VoiceSearch, SearchBox } from 'react-instantsearch-dom'; -import { WrapWithHits } from './util'; - -const stories = storiesOf('VoiceSearch', module); - -stories - .add('default', () => ( - -

- To see this button disabled, test it on unsupported browsers like - Safari, Firefox, etc. -

- -
- )) - .add('without status', () => ( - - null} /> - - )) - .add('with a SearchBox', () => ( - - - - - )) - .add('with a custom button text', () => { - const style = window.document.createElement('style'); - window.document.head.appendChild(style); - [ - `.custom-button-story .ais-VoiceSearch-button:hover { - background: inherit; - }`, - ].forEach(rule => style.sheet.insertRule(rule)); - return ( -
- - (isListening ? '⏹' : '🎙')} - /> - -
- ); - }); diff --git a/stories/VoiceSearch.stories.tsx b/stories/VoiceSearch.stories.tsx new file mode 100644 index 0000000000..62d7dcc922 --- /dev/null +++ b/stories/VoiceSearch.stories.tsx @@ -0,0 +1,175 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { VoiceSearch, SearchBox } from 'react-instantsearch-dom'; +import { WrapWithHits } from './util'; + +const stories = storiesOf('VoiceSearch', module); + +stories + .add('default', () => ( + +

+ To see this button disabled, test it on unsupported browsers like + Safari, Firefox, etc. +

+ +
+ )) + .add('without status', () => ( + + null} /> + + )) + .add('with a SearchBox', () => ( + + + + + )) + .add('with a custom button text', () => { + const style = window.document.createElement('style'); + window.document.head.appendChild(style); + [ + `.custom-button-story .ais-VoiceSearch-button:hover { + background: inherit; + }`, + ].forEach(rule => (style.sheet as CSSStyleSheet).insertRule(rule)); + return ( + +
+ (isListening ? '⏹' : '🎙')} + /> +
+
+ ); + }) + .add('with full status', () => { + const Status = ({ + status, + errorCode, + isListening, + transcript, + isSpeechFinal, + isBrowserSupported, + }) => { + return ( +
+

status: {status}

+

errorCode: {errorCode}

+

isListening: {isListening ? 'true' : 'false'}

+

transcript: {transcript}

+

isSpeechFinal: {isSpeechFinal ? 'true' : 'false'}

+

isBrowserSupported: {isBrowserSupported ? 'true' : 'false'}

+
+ ); + }; + + return ( + + + + ); + }) + .add('search as you speak', () => { + const Status = ({ + status, + errorCode, + isListening, + transcript, + isSpeechFinal, + isBrowserSupported, + }) => { + return ( +
+

status: {status}

+

errorCode: {errorCode}

+

isListening: {isListening ? 'true' : 'false'}

+

transcript: {transcript}

+

isSpeechFinal: {isSpeechFinal ? 'true' : 'false'}

+

isBrowserSupported: {isBrowserSupported ? 'true' : 'false'}

+
+ ); + }; + return ( + + + + ); + }) + .add('example of dynamic UI working with SearchBox', () => { + const style = window.document.createElement('style'); + window.document.head.appendChild(style); + [ + `.custom-ui .ais-VoiceSearch-button { + position: absolute; + right: 43px; + top: 53px; + z-index: 3; + }`, + `.custom-ui .ais-VoiceSearch-status .layer { + position: absolute; + background: rgba(255, 255, 255, 0.95); + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 2; + align-items: center; + justify-content: center; + display: none; + }`, + `.custom-ui .ais-VoiceSearch-status .layer.listening-true { + display: flex; + }`, + `.custom-ui .ais-VoiceSearch-status .layer span { + font-size: 2rem; + color: #555; + }`, + ].forEach(rule => (style.sheet as CSSStyleSheet).insertRule(rule)); + + const Status = ({ isListening, transcript }) => { + return ( +
+ {transcript} +
+ ); + }; + + return ( + +
+ + null} /> +
+
+ ); + }); From 31cf98574834a7e8bad29ee52aa9a2b0358c4bac Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Wed, 17 Apr 2019 17:14:55 +0200 Subject: [PATCH 32/66] fix(voiceSearch): provide one voiceSearchHelper instancer per component --- .../src/connectors/connectVoiceSearch.ts | 74 +++++++++---------- .../src/lib/voiceSearchHelper/index.ts | 29 +++----- 2 files changed, 46 insertions(+), 57 deletions(-) diff --git a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts index 1124cad27d..fcf6ada4d3 100644 --- a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts +++ b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts @@ -26,47 +26,45 @@ function getCurrentRefinement(props, searchState, context) { ); } -export default Composed => { - const voiceSearch = voiceSearchHelper(); +export default createConnector({ + displayName: 'AlgoliaVoiceSearch', - const connector = createConnector({ - displayName: 'AlgoliaVoiceSearch', - - getProvidedProps({ searchAsYouSpeak = false } = {}) { - voiceSearch.setSearchAsYouSpeak(searchAsYouSpeak); - voiceSearch.setOnQueryChange(query => { - this.refine(query); - }); - voiceSearch.setOnStateChange(() => { - this.context.ais.widgetsManager.update(); + getProvidedProps({ searchAsYouSpeak = false } = {}) { + if (!this._voiceSearch) { + this._voiceSearch = voiceSearchHelper({ + searchAsYouSpeak, + onQueryChange: query => { + this.refine(query); + }, + onStateChange: () => { + this.context.ais.widgetsManager.update(); + }, }); + } - const { - getState, - isBrowserSupported, - isListening, - toggleListening, - } = voiceSearch; - - return { - isBrowserSupported: isBrowserSupported(), - isListening: isListening(), - toggleListening, - voiceListeningState: getState(), - searchAsYouSpeak, - }; - }, + const { + getState, + isBrowserSupported, + isListening, + toggleListening, + } = this._voiceSearch; - refine(_0, searchState, nextRefinement) { - return refine(searchState, nextRefinement, this.context); - }, + return { + isBrowserSupported: isBrowserSupported(), + isListening: isListening(), + toggleListening, + voiceListeningState: getState(), + searchAsYouSpeak, + }; + }, - getSearchParameters(searchParameters, props, searchState) { - return searchParameters.setQuery( - getCurrentRefinement(props, searchState, this.context) - ); - }, - }); + refine(_0, searchState, nextRefinement) { + return refine(searchState, nextRefinement, this.context); + }, - return connector(Composed); -}; + getSearchParameters(searchParameters, props, searchState) { + return searchParameters.setQuery( + getCurrentRefinement(props, searchState, this.context) + ); + }, +}); diff --git a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts index 1ebfa5d5b8..061611961d 100644 --- a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts @@ -5,8 +5,11 @@ const STATUS_RECOGNIZING = 'recognizing'; const STATUS_FINISHED = 'finished'; const STATUS_ERROR = 'error'; -type QueryChangeListener = (query: string) => void; -type StateChangeListener = () => void; +export type VoiceSearchHelperParams = { + searchAsYouSpeak: boolean; + onQueryChange: (query: string) => void; + onStateChange: () => void; +}; export type VoiceListeningState = { status: string; @@ -17,7 +20,11 @@ export type VoiceListeningState = { export type ToggleListening = () => void; -export default function voiceSearchHelper() { +export default function voiceSearchHelper({ + searchAsYouSpeak, + onQueryChange, + onStateChange, +}: VoiceSearchHelperParams) { const SpeechRecognitionAPI: new () => SpeechRecognition = (window as any).webkitSpeechRecognition || (window as any).SpeechRecognition; @@ -29,19 +36,6 @@ export default function voiceSearchHelper() { }); let state: VoiceListeningState = getDefaultState(STATUS_INITIAL); let recognition: SpeechRecognition | undefined; - let searchAsYouSpeak: boolean = false; - let onQueryChange: QueryChangeListener = () => {}; - let onStateChange: StateChangeListener = () => {}; - - const setOnQueryChange = (queryChangeListener: QueryChangeListener) => { - onQueryChange = queryChangeListener; - }; - const setOnStateChange = (stateChangeListener: StateChangeListener) => { - onStateChange = stateChangeListener; - }; - const setSearchAsYouSpeak = (value: boolean) => { - searchAsYouSpeak = value; - }; const isBrowserSupported = () => Boolean(SpeechRecognitionAPI); @@ -121,9 +115,6 @@ export default function voiceSearchHelper() { }; return { - setOnQueryChange, - setOnStateChange, - setSearchAsYouSpeak, getState, isBrowserSupported, isListening, From 38d89edfebf7df5c74d2dcac1e88a81ccff9581b Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Thu, 18 Apr 2019 16:27:37 +0200 Subject: [PATCH 33/66] refactor(voiceSearch): move voiceSearchHelper-related logic from the connector to the component --- .../src/connectors/connectVoiceSearch.ts | 70 ----------- .../react-instantsearch-core/src/index.js | 1 - .../src/components/VoiceSearch.tsx | 119 +++++++++--------- .../src/lib/voiceSearchHelper/index.ts | 9 +- .../src/widgets/VoiceSearch.ts | 4 +- stories/VoiceSearch.stories.tsx | 2 +- 6 files changed, 72 insertions(+), 133 deletions(-) delete mode 100644 packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts rename packages/{react-instantsearch-core => react-instantsearch-dom}/src/lib/voiceSearchHelper/index.ts (93%) diff --git a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts b/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts deleted file mode 100644 index fcf6ada4d3..0000000000 --- a/packages/react-instantsearch-core/src/connectors/connectVoiceSearch.ts +++ /dev/null @@ -1,70 +0,0 @@ -import createConnector from '../core/createConnector'; -import voiceSearchHelper from '../lib/voiceSearchHelper'; -import { refineValue, getCurrentRefinementValue } from '../core/indexUtils'; - -function getId() { - return 'query'; -} - -function refine(searchState, nextRefinement, context) { - const id = getId(); - const nextValue = { [id]: nextRefinement }; - const resetPage = true; - return refineValue(searchState, nextValue, context, resetPage); -} - -function getCurrentRefinement(props, searchState, context) { - const id = getId(); - const refinementsCallback = currentRefinement => currentRefinement || ''; - return getCurrentRefinementValue( - props, - searchState, - context, - id, - '', - refinementsCallback - ); -} - -export default createConnector({ - displayName: 'AlgoliaVoiceSearch', - - getProvidedProps({ searchAsYouSpeak = false } = {}) { - if (!this._voiceSearch) { - this._voiceSearch = voiceSearchHelper({ - searchAsYouSpeak, - onQueryChange: query => { - this.refine(query); - }, - onStateChange: () => { - this.context.ais.widgetsManager.update(); - }, - }); - } - - const { - getState, - isBrowserSupported, - isListening, - toggleListening, - } = this._voiceSearch; - - return { - isBrowserSupported: isBrowserSupported(), - isListening: isListening(), - toggleListening, - voiceListeningState: getState(), - searchAsYouSpeak, - }; - }, - - refine(_0, searchState, nextRefinement) { - return refine(searchState, nextRefinement, this.context); - }, - - getSearchParameters(searchParameters, props, searchState) { - return searchParameters.setQuery( - getCurrentRefinement(props, searchState, this.context) - ); - }, -}); diff --git a/packages/react-instantsearch-core/src/index.js b/packages/react-instantsearch-core/src/index.js index e88591ae87..8cbadcbbb3 100644 --- a/packages/react-instantsearch-core/src/index.js +++ b/packages/react-instantsearch-core/src/index.js @@ -50,7 +50,6 @@ export { default as connectStats } from './connectors/connectStats'; export { default as connectToggleRefinement, } from './connectors/connectToggleRefinement'; -export { default as connectVoiceSearch } from './connectors/connectVoiceSearch'; export { default as connectHitInsights } from './connectors/connectHitInsights'; // Types diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 9b17c79d1e..b65414f868 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -1,19 +1,12 @@ -import React from 'react'; +import React, { Component } from 'react'; import { translatable, Translate } from 'react-instantsearch-core'; import { createClassNames } from '../core/utils'; +import voiceSearchHelper, { + VoiceSearchHelper, + VoiceListeningState, +} from '../lib/voiceSearchHelper'; const cx = createClassNames('VoiceSearch'); -type VoiceListeningState = { - status: string; - transcript?: string; - isSpeechFinal?: boolean; - errorCode?: string; -}; - -type ToggleListening = (toggleListeningParams: { - searchAsYouSpeak: boolean; -}) => void; - type InnerComponentProps = { status: string; errorCode?: string; @@ -24,12 +17,9 @@ type InnerComponentProps = { }; type VoiceSearchProps = { - isBrowserSupported: boolean; - isListening: boolean; - toggleListening: ToggleListening; - voiceListeningState: VoiceListeningState; searchAsYouSpeak?: boolean; + refine: (query: string) => void; translate: Translate; buttonComponent?: React.FC; statusComponent?: React.FC; @@ -86,54 +76,67 @@ const DefaultButton: React.FC = ({ ); }; -const DefaultStatus: React.FC = ({ status }) => ( -

{status}

+const DefaultStatus: React.FC = ({ transcript }) => ( +

{transcript}

); -const VoiceSearch = ({ - translate, - isBrowserSupported, - isListening, - toggleListening, - voiceListeningState, - searchAsYouSpeak = false, - - buttonComponent: Button = DefaultButton, - statusComponent: Status = DefaultStatus, -}: VoiceSearchProps) => { - const onClick = (event: React.MouseEvent) => { - event.currentTarget.blur(); - toggleListening({ searchAsYouSpeak }); - }; +class VoiceSearch extends Component { + private voiceSearch: VoiceSearchHelper; - const { status, transcript, isSpeechFinal, errorCode } = voiceListeningState; + constructor(props: VoiceSearchProps) { + super(props); + const { searchAsYouSpeak = false, refine } = props; + this.voiceSearch = voiceSearchHelper({ + searchAsYouSpeak, + onQueryChange: query => refine(query), + onStateChange: () => { + this.setState(this.voiceSearch.getState()); + }, + }); + this.state = this.voiceSearch.getState(); + } - const innerProps = { - status, - errorCode, - isListening, - transcript, - isSpeechFinal, - isBrowserSupported, - }; + public render() { + const { status, transcript, isSpeechFinal, errorCode } = this.state; + const { isListening, isBrowserSupported } = this.voiceSearch; + const { + translate, + buttonComponent: Button = DefaultButton, + statusComponent: Status = DefaultStatus, + } = this.props; + const innerProps: InnerComponentProps = { + status, + errorCode, + isListening: isListening(), + transcript, + isSpeechFinal, + isBrowserSupported: isBrowserSupported(), + }; - return ( -
- -
- + return ( +
+ +
+ +
-
- ); -}; + ); + } + + private onClick = (event: React.MouseEvent) => { + event.currentTarget.blur(); + const { toggleListening } = this.voiceSearch; + toggleListening(); + }; +} export default translatable({ buttonTitle: 'Search by voice', diff --git a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts similarity index 93% rename from packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts rename to packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts index 061611961d..7b31b334f4 100644 --- a/packages/react-instantsearch-core/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts @@ -20,11 +20,18 @@ export type VoiceListeningState = { export type ToggleListening = () => void; +export type VoiceSearchHelper = { + getState: () => VoiceListeningState; + isBrowserSupported: () => boolean; + isListening: () => boolean; + toggleListening: () => void; +}; + export default function voiceSearchHelper({ searchAsYouSpeak, onQueryChange, onStateChange, -}: VoiceSearchHelperParams) { +}: VoiceSearchHelperParams): VoiceSearchHelper { const SpeechRecognitionAPI: new () => SpeechRecognition = (window as any).webkitSpeechRecognition || (window as any).SpeechRecognition; diff --git a/packages/react-instantsearch-dom/src/widgets/VoiceSearch.ts b/packages/react-instantsearch-dom/src/widgets/VoiceSearch.ts index 66db565290..d671030e87 100644 --- a/packages/react-instantsearch-dom/src/widgets/VoiceSearch.ts +++ b/packages/react-instantsearch-dom/src/widgets/VoiceSearch.ts @@ -1,4 +1,4 @@ -import { connectVoiceSearch } from 'react-instantsearch-core'; +import { connectSearchBox } from 'react-instantsearch-core'; import VoiceSearch from '../components/VoiceSearch'; -export default connectVoiceSearch(VoiceSearch); +export default connectSearchBox(VoiceSearch); diff --git a/stories/VoiceSearch.stories.tsx b/stories/VoiceSearch.stories.tsx index 62d7dcc922..f96be03169 100644 --- a/stories/VoiceSearch.stories.tsx +++ b/stories/VoiceSearch.stories.tsx @@ -168,7 +168,7 @@ stories >
- null} /> +
); From 626e44623bd96e3ad406888d6c1a56315078d70b Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Fri, 19 Apr 2019 17:31:41 +0200 Subject: [PATCH 34/66] test(voiceSearch): fix tests for the component --- .../src/components/__tests__/VoiceSearch.tsx | 99 +++---- .../__snapshots__/VoiceSearch.tsx.snap | 272 ++++++++++++++---- 2 files changed, 256 insertions(+), 115 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx index a3e537c8e5..5811de3088 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx @@ -1,71 +1,57 @@ import React from 'react'; -import Enzyme, { mount, shallow } from 'enzyme'; +import Enzyme, { mount } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import VoiceSearch from '../VoiceSearch'; -Enzyme.configure({ adapter: new Adapter() }); +const mockGetState = jest.fn(); +const mockIsBrowserSupported = jest.fn(); +const mockIsListening = jest.fn(); +const mockToggleListening = jest.fn(); -const defaultProps = { - isBrowserSupported: true, - isListening: false, - toggleListening: () => {}, - voiceListeningState: { - status: 'initial', - transcript: undefined, - isSpeechFinal: undefined, - errorCode: undefined, - }, -}; +jest.mock('../../lib/voiceSearchHelper', () => { + return () => { + return { + getState: mockGetState.mockImplementation(() => ({})), + isBrowserSupported: mockIsBrowserSupported, + isListening: mockIsListening, + toggleListening: mockToggleListening, + }; + }; +}); + +Enzyme.configure({ adapter: new Adapter() }); describe('VoiceSearch', () => { describe('button', () => { it('calls toggleListening when button is clicked', () => { - const props = { - ...defaultProps, - toggleListening: jest.fn(), - }; - const wrapper = mount(); + const wrapper = mount(); wrapper.find('button').simulate('click'); - expect(props.toggleListening).toHaveBeenCalledTimes(1); + expect(mockToggleListening).toHaveBeenCalledTimes(1); }); }); describe('Rendering', () => { it('with default props', () => { - const wrapper = shallow(); + const wrapper = mount(); expect(wrapper).toMatchSnapshot(); }); it('with custom component for button with isListening: false', () => { - const customButton = ({ isListening }) => ( - - ); + const customButton = ({ isListening }) => + isListening ? 'Stop' : 'Start'; - const wrapper = shallow( - - ); + const wrapper = mount(); + expect(wrapper.find('button').text()).toBe('Start'); expect(wrapper).toMatchSnapshot(); }); it('with custom component for button with isListening: true', () => { - const customButton = ({ isListening }) => ( - - ); - - const props = { - ...defaultProps, - isListening: true, - voiceListeningState: { - status: 'recognizing', - transcript: undefined, - isSpeechFinal: undefined, - errorCode: undefined, - }, - }; + const customButton = ({ isListening }) => + isListening ? 'Stop' : 'Start'; + mockIsListening.mockImplementationOnce(() => true); - const wrapper = shallow( - - ); + const wrapper = mount(); + expect(wrapper.find('button').text()).toBe('Stop'); expect(wrapper).toMatchSnapshot(); }); @@ -81,27 +67,22 @@ describe('VoiceSearch', () => {

status: {status}

errorCode: {errorCode}

-

isListening: {isListening}

+

isListening: {isListening ? 'true' : 'false'}

transcript: {transcript}

-

isSpeechFinal: {isSpeechFinal}

-

isBrowserSupported: {isBrowserSupported}

+

isSpeechFinal: {isSpeechFinal ? 'true' : 'false'}

+

isBrowserSupported: {isBrowserSupported ? 'true' : 'false'}

); - const props = { - ...defaultProps, - isListening: true, - voiceListeningState: { - status: 'recognizing', - transcript: 'Hello', - isSpeechFinal: false, - errorCode: undefined, - }, - }; + mockIsListening.mockImplementationOnce(() => true); + mockGetState.mockImplementationOnce(() => ({ + status: 'recognizing', + transcript: 'Hello', + isSpeechFinal: false, + errorCode: undefined, + })); - const wrapper = shallow( - - ); + const wrapper = mount(); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap index fb1a66f270..295b9d4b96 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap +++ b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap @@ -1,72 +1,232 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`VoiceSearch Rendering with custom component for button with isListening: false 1`] = ` - +> + +
+ +
+ +

+ +

+
+
+ `; exports[`VoiceSearch Rendering with custom component for button with isListening: true 1`] = ` - +> + +
+ +
+ +

+ +

+
+
+ `; exports[`VoiceSearch Rendering with custom template for status 1`] = ` - +> + +
+ +
+ +
+

+ status: + recognizing +

+

+ errorCode: +

+

+ isListening: + true +

+

+ transcript: + Hello +

+

+ isSpeechFinal: + false +

+

+ isBrowserSupported: + false +

+
+
+
+
+
+ `; exports[`VoiceSearch Rendering with default props 1`] = ` - + + +
+ +
+ +

+ +

+
+
+
`; From ec305c5f8875c03624eb2bcce780e45ad6acd9e7 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Fri, 19 Apr 2019 17:44:14 +0200 Subject: [PATCH 35/66] test(voiceSearch): add tests for VoiceSearchHelper (copied from IS.js) --- .../lib/voiceSearchHelper/__tests__/index.ts | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts diff --git a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts new file mode 100644 index 0000000000..fa0057a99c --- /dev/null +++ b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts @@ -0,0 +1,172 @@ +import VoiceSearchHelper, { VoiceSearchHelperParams } from '..'; + +// copied from InstantSearch.js + +const getHelper = (opts?: VoiceSearchHelperParams) => + VoiceSearchHelper( + opts || { + searchAsYouSpeak: false, + onQueryChange: () => {}, + onStateChange: () => {}, + } + ); + +type DummySpeechRecognition = () => void; +declare global { + interface Window { + webkitSpeechRecognition?: SpeechRecognition | DummySpeechRecognition; + SpeechRecognition?: SpeechRecognition | DummySpeechRecognition; + } +} + +describe('VoiceSearchHelper', () => { + beforeEach(() => { + delete window.webkitSpeechRecognition; + delete window.SpeechRecognition; + }); + + it('has initial state correctly', () => { + const helper = getHelper(); + expect(helper.getState()).toEqual({ + errorCode: undefined, + isSpeechFinal: undefined, + status: 'initial', + transcript: undefined, + }); + }); + + it('is not supported', () => { + const helper = getHelper(); + expect(helper.isBrowserSupported()).toBe(false); + }); + + it('is not listening', () => { + const helper = getHelper(); + expect(helper.isListening()).toBe(false); + }); + + it('is supported with webkitSpeechRecognition', () => { + window.webkitSpeechRecognition = () => {}; + const helper = getHelper(); + expect(helper.isBrowserSupported()).toBe(true); + }); + + it('is supported with SpeechRecognition', () => { + window.SpeechRecognition = () => {}; + const helper = getHelper(); + expect(helper.isBrowserSupported()).toBe(true); + }); + + it('works with mock SpeechRecognition (searchAsYouSpeak:false)', () => { + let recognition; + window.SpeechRecognition = jest.fn().mockImplementation(() => ({ + start() { + /* eslint-disable-next-line consistent-this */ + recognition = this; + }, + })); + const onQueryChange = jest.fn(); + const onStateChange = jest.fn(); + const helper = getHelper({ + searchAsYouSpeak: false, + onQueryChange, + onStateChange, + }); + const { getState } = helper; + helper.toggleListening(); + expect(onStateChange).toHaveBeenCalledTimes(1); + expect(getState().status).toEqual('askingPermission'); + recognition.onstart(); + expect(getState().status).toEqual('waiting'); + recognition.onresult({ + results: [ + (() => { + const obj = [ + { + transcript: 'Hello World', + }, + ]; + (obj as any).isFinal = true; + return obj; + })(), + ], + }); + expect(getState().status).toEqual('recognizing'); + expect(getState().transcript).toEqual('Hello World'); + expect(getState().isSpeechFinal).toBe(true); + expect(onQueryChange).toHaveBeenCalledTimes(0); + recognition.onend(); + expect(onQueryChange).toHaveBeenCalledWith('Hello World'); + expect(getState().status).toEqual('finished'); + }); + + it('works with mock SpeechRecognition (searchAsYouSpeak:true)', () => { + let recognition; + window.SpeechRecognition = jest.fn().mockImplementation(() => ({ + start() { + /* eslint-disable-next-line consistent-this */ + recognition = this; + }, + })); + const onQueryChange = jest.fn(); + const onStateChange = jest.fn(); + const helper = getHelper({ + searchAsYouSpeak: true, + onQueryChange, + onStateChange, + }); + const { getState } = helper; + helper.toggleListening(); + expect(onStateChange).toHaveBeenCalledTimes(1); + expect(getState().status).toEqual('askingPermission'); + recognition.onstart(); + expect(getState().status).toEqual('waiting'); + recognition.onresult({ + results: [ + (() => { + const obj = [ + { + transcript: 'Hello World', + }, + ]; + (obj as any).isFinal = true; + return obj; + })(), + ], + }); + expect(getState().status).toEqual('recognizing'); + expect(getState().transcript).toEqual('Hello World'); + expect(getState().isSpeechFinal).toBe(true); + expect(onQueryChange).toHaveBeenCalledWith('Hello World'); + recognition.onend(); + expect(onQueryChange).toHaveBeenCalledTimes(1); + expect(getState().status).toEqual('finished'); + }); + + it('works with onerror', () => { + let recognition; + window.SpeechRecognition = jest.fn().mockImplementation(() => ({ + start() { + /* eslint-disable-next-line consistent-this */ + recognition = this; + }, + })); + const onQueryChange = jest.fn(); + const onStateChange = jest.fn(); + const helper = getHelper({ + searchAsYouSpeak: true, + onQueryChange, + onStateChange, + }); + const { getState } = helper; + helper.toggleListening(); + expect(getState().status).toEqual('askingPermission'); + recognition.onerror({ + error: 'not-allowed', + }); + expect(getState().status).toEqual('error'); + expect(getState().errorCode).toEqual('not-allowed'); + recognition.onend(); + expect(onQueryChange).toHaveBeenCalledTimes(0); + }); +}); From f623c5a604048d78c22c43224b19ada30d6178c1 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 23 Apr 2019 10:25:27 +0200 Subject: [PATCH 36/66] chore(voiceSearch): increase limit of bundlesize --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4e316f722d..4d44701211 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ }, { "path": "packages/react-instantsearch/dist/umd/Dom.min.js", - "maxSize": "63.00 kB" + "maxSize": "63.81 kB" }, { "path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js", @@ -142,7 +142,7 @@ }, { "path": "packages/react-instantsearch-dom/dist/umd/ReactInstantSearchDOM.min.js", - "maxSize": "62.75 kB" + "maxSize": "63.58 kB" }, { "path": "packages/react-instantsearch-dom-maps/dist/umd/ReactInstantSearchDOMMaps.min.js", From a1808db9a710722ee098d4a70e0c5111a14ddc5e Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 23 Apr 2019 14:53:27 +0200 Subject: [PATCH 37/66] test(voiceSearch): improve tests --- .../src/components/VoiceSearch.tsx | 9 +- .../src/components/__tests__/VoiceSearch.tsx | 28 ++- .../__snapshots__/VoiceSearch.tsx.snap | 206 ++++-------------- 3 files changed, 66 insertions(+), 177 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index b65414f868..300a740220 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -118,9 +118,13 @@ class VoiceSearch extends Component { @@ -140,4 +144,5 @@ class VoiceSearch extends Component { export default translatable({ buttonTitle: 'Search by voice', + disabledButtonTitle: 'Search by voice (not supported on this browser)', })(VoiceSearch); diff --git a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx index 5811de3088..53b5d03fc8 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx @@ -3,15 +3,15 @@ import Enzyme, { mount } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import VoiceSearch from '../VoiceSearch'; -const mockGetState = jest.fn(); -const mockIsBrowserSupported = jest.fn(); +const mockGetState = jest.fn().mockImplementation(() => ({})); +const mockIsBrowserSupported = jest.fn().mockImplementation(() => true); const mockIsListening = jest.fn(); const mockToggleListening = jest.fn(); jest.mock('../../lib/voiceSearchHelper', () => { return () => { return { - getState: mockGetState.mockImplementation(() => ({})), + getState: mockGetState, isBrowserSupported: mockIsBrowserSupported, isListening: mockIsListening, toggleListening: mockToggleListening, @@ -42,17 +42,25 @@ describe('VoiceSearch', () => { const wrapper = mount(); expect(wrapper.find('button').text()).toBe('Start'); - expect(wrapper).toMatchSnapshot(); }); it('with custom component for button with isListening: true', () => { const customButton = ({ isListening }) => isListening ? 'Stop' : 'Start'; - mockIsListening.mockImplementationOnce(() => true); + mockIsListening.mockImplementation(() => true); const wrapper = mount(); expect(wrapper.find('button').text()).toBe('Stop'); - expect(wrapper).toMatchSnapshot(); + mockIsListening.mockClear(); + }); + + it('renders a specific title when it is disabled', () => { + mockIsBrowserSupported.mockImplementation(() => false); + const wrapper = mount(); + expect(wrapper.find('button').prop('title')).toBe( + 'Search by voice (not supported on this browser)' + ); + mockIsBrowserSupported.mockImplementation(() => true); }); it('with custom template for status', () => { @@ -74,8 +82,8 @@ describe('VoiceSearch', () => {
); - mockIsListening.mockImplementationOnce(() => true); - mockGetState.mockImplementationOnce(() => ({ + mockIsListening.mockImplementation(() => true); + mockGetState.mockImplementation(() => ({ status: 'recognizing', transcript: 'Hello', isSpeechFinal: false, @@ -83,7 +91,9 @@ describe('VoiceSearch', () => { })); const wrapper = mount(); - expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('.ais-VoiceSearch-status')).toMatchSnapshot(); + mockIsListening.mockClear(); + mockGetState.mockClear(); }); }); }); diff --git a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap index 295b9d4b96..af767dc6a2 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap +++ b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap @@ -1,173 +1,43 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`VoiceSearch Rendering with custom component for button with isListening: false 1`] = ` - - -
- -
- -

- -

-
-
-
-`; - -exports[`VoiceSearch Rendering with custom component for button with isListening: true 1`] = ` - - -
- -
- -

- -

-
-
-
-`; - exports[`VoiceSearch Rendering with custom template for status 1`] = ` - - -
- -
- -
-

- status: - recognizing -

-

- errorCode: -

-

- isListening: - true -

-

- transcript: - Hello -

-

- isSpeechFinal: - false -

-

- isBrowserSupported: - false -

-
-
-
+
+

+ status: + recognizing +

+

+ errorCode: +

+

+ isListening: + true +

+

+ transcript: + Hello +

+

+ isSpeechFinal: + false +

+

+ isBrowserSupported: + true +

- - + +
`; exports[`VoiceSearch Rendering with default props 1`] = ` @@ -185,7 +55,9 @@ exports[`VoiceSearch Rendering with default props 1`] = ` title="Search by voice" type="button" > - + - +

From 870dad64441152cec8c0df8b6a840b6ef8042421 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 29 Apr 2019 10:30:29 +0200 Subject: [PATCH 38/66] chore(voiceSearch): increase bundlesize limit --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4d44701211..0f79da3eed 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ }, { "path": "packages/react-instantsearch/dist/umd/Dom.min.js", - "maxSize": "63.81 kB" + "maxSize": "63.84 kB" }, { "path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js", @@ -142,7 +142,7 @@ }, { "path": "packages/react-instantsearch-dom/dist/umd/ReactInstantSearchDOM.min.js", - "maxSize": "63.58 kB" + "maxSize": "63.62 kB" }, { "path": "packages/react-instantsearch-dom-maps/dist/umd/ReactInstantSearchDOMMaps.min.js", From 69e558290bd39e654f206f18d958b88ecbea6ce3 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 29 Apr 2019 11:16:40 +0200 Subject: [PATCH 39/66] chore(voiceSearch): increase limit of bundlesize --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0f79da3eed..f7cf07f78f 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ }, { "path": "packages/react-instantsearch/dist/umd/Dom.min.js", - "maxSize": "63.84 kB" + "maxSize": "64.10 kB" }, { "path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js", @@ -142,7 +142,7 @@ }, { "path": "packages/react-instantsearch-dom/dist/umd/ReactInstantSearchDOM.min.js", - "maxSize": "63.62 kB" + "maxSize": "63.90 kB" }, { "path": "packages/react-instantsearch-dom-maps/dist/umd/ReactInstantSearchDOMMaps.min.js", From 918ef36bf50f77de97490c7913b90ae969f9fedb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 6 May 2019 14:39:25 +0200 Subject: [PATCH 40/66] Update packages/react-instantsearch-dom/src/components/VoiceSearch.tsx Co-Authored-By: eunjae-lee --- packages/react-instantsearch-dom/src/components/VoiceSearch.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 300a740220..895cb9fd7a 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -18,7 +18,6 @@ type InnerComponentProps = { type VoiceSearchProps = { searchAsYouSpeak?: boolean; - refine: (query: string) => void; translate: Translate; buttonComponent?: React.FC; From 7ca40f2bae3af203a33eeb4031783bd291d40643 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 6 May 2019 14:48:59 +0200 Subject: [PATCH 41/66] chore(voiceSearch): update voiceSearchHelper from IS.js --- .../lib/voiceSearchHelper/__tests__/index.ts | 11 ++++++---- .../src/lib/voiceSearchHelper/index.ts | 20 ++++++++++--------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts index fa0057a99c..dabddce65f 100644 --- a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts +++ b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts @@ -1,9 +1,12 @@ -import VoiceSearchHelper, { VoiceSearchHelperParams } from '..'; +// copied from https://github.com/algolia/instantsearch.js/blob/e904ad689d8300b829aff928bbed8e4cdbe37b7b/src/lib/voiceSearchHelper/__tests__/index-test.ts -// copied from InstantSearch.js +import voiceSearchHelper, { + VoiceSearchHelper, + VoiceSearchHelperParams, +} from '..'; -const getHelper = (opts?: VoiceSearchHelperParams) => - VoiceSearchHelper( +const getHelper = (opts?: VoiceSearchHelperParams): VoiceSearchHelper => + voiceSearchHelper( opts || { searchAsYouSpeak: false, onQueryChange: () => {}, diff --git a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts index 7b31b334f4..800cd43cc0 100644 --- a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts @@ -1,3 +1,5 @@ +// copied from https://github.com/algolia/instantsearch.js/blob/e904ad689d8300b829aff928bbed8e4cdbe37b7b/src/lib/voiceSearchHelper/index.ts + const STATUS_INITIAL = 'initial'; const STATUS_ASKING_PERMISSION = 'askingPermission'; const STATUS_WAITING = 'waiting'; @@ -18,8 +20,6 @@ export type VoiceListeningState = { errorCode?: string; }; -export type ToggleListening = () => void; - export type VoiceSearchHelper = { getState: () => VoiceListeningState; isBrowserSupported: () => boolean; @@ -27,6 +27,8 @@ export type VoiceSearchHelper = { toggleListening: () => void; }; +export type ToggleListening = () => void; + export default function voiceSearchHelper({ searchAsYouSpeak, onQueryChange, @@ -44,25 +46,25 @@ export default function voiceSearchHelper({ let state: VoiceListeningState = getDefaultState(STATUS_INITIAL); let recognition: SpeechRecognition | undefined; - const isBrowserSupported = () => Boolean(SpeechRecognitionAPI); + const isBrowserSupported = (): boolean => Boolean(SpeechRecognitionAPI); - const isListening = () => + const isListening = (): boolean => state.status === STATUS_ASKING_PERMISSION || state.status === STATUS_WAITING || state.status === STATUS_RECOGNIZING; - const setState = (newState = {}) => { + const setState = (newState = {}): void => { state = { ...state, ...newState }; onStateChange(); }; const getState = (): VoiceListeningState => state; - const resetState = (status = STATUS_INITIAL) => { + const resetState = (status = STATUS_INITIAL): void => { setState(getDefaultState(status)); }; - const stop = () => { + const stop = (): void => { if (recognition) { recognition.stop(); recognition = undefined; @@ -70,7 +72,7 @@ export default function voiceSearchHelper({ resetState(); }; - const start = () => { + const start = (): void => { recognition = new SpeechRecognitionAPI(); if (!recognition) { return; @@ -110,7 +112,7 @@ export default function voiceSearchHelper({ recognition.start(); }; - const toggleListening = () => { + const toggleListening = (): void => { if (!isBrowserSupported()) { return; } From bf5d46b3b1419a61951ba69762b852f92b3d2910 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 14 May 2019 17:10:50 +0200 Subject: [PATCH 42/66] chore(voiceSearch): change code using Fragment --- .../src/components/VoiceSearch.tsx | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 895cb9fd7a..12d2722b51 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -24,41 +24,30 @@ type VoiceSearchProps = { statusComponent?: React.FC; }; -const getDefaultButtonInnerElement = ( - status: string, - errorCode: string | undefined, - isListening: boolean -) => { - if (status === 'error' && errorCode === 'not-allowed') { - return ( - <> - - - - - - - ); - } - return ( - <> - - - - - - ); -}; - const DefaultButton: React.FC = ({ status, errorCode, isListening, }) => { - return ( + return status === 'error' && errorCode === 'not-allowed' ? ( + + + + + + + + ) : ( = ({ strokeLinecap="round" strokeLinejoin="round" > - {getDefaultButtonInnerElement(status, errorCode, isListening)} + + + + ); }; From 64ff9878a843de919722c8fbe6eff8caeada5674 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 14 May 2019 18:09:51 +0200 Subject: [PATCH 43/66] type(voiceSearch): Update packages/react-instantsearch-core/src/types/translatable.ts Co-Authored-By: Samuel Vaillant --- packages/react-instantsearch-core/src/types/translatable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-core/src/types/translatable.ts b/packages/react-instantsearch-core/src/types/translatable.ts index 05f0d9233f..03f8f7c34b 100644 --- a/packages/react-instantsearch-core/src/types/translatable.ts +++ b/packages/react-instantsearch-core/src/types/translatable.ts @@ -1 +1 @@ -export type Translate = (key: string, ...params: string[]) => string; +export type Translate = (key: string, ...params: any[]) => string; From 100e0c0eea0018d407c248c1351e1467c60869ac Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 14 May 2019 18:16:31 +0200 Subject: [PATCH 44/66] chore(voiceSearch): use defaultProps --- .../src/components/VoiceSearch.tsx | 12 ++++++++---- .../__tests__/__snapshots__/VoiceSearch.tsx.snap | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 12d2722b51..7a5c87ab6f 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -20,8 +20,8 @@ type VoiceSearchProps = { searchAsYouSpeak?: boolean; refine: (query: string) => void; translate: Translate; - buttonComponent?: React.FC; - statusComponent?: React.FC; + buttonComponent: React.FC; + statusComponent: React.FC; }; const DefaultButton: React.FC = ({ @@ -75,6 +75,10 @@ const DefaultStatus: React.FC = ({ transcript }) => ( ); class VoiceSearch extends Component { + static defaultProps = { + buttonComponent: DefaultButton, + statusComponent: DefaultStatus, + }; private voiceSearch: VoiceSearchHelper; constructor(props: VoiceSearchProps) { @@ -95,8 +99,8 @@ class VoiceSearch extends Component { const { isListening, isBrowserSupported } = this.voiceSearch; const { translate, - buttonComponent: Button = DefaultButton, - statusComponent: Status = DefaultStatus, + buttonComponent: Button, + statusComponent: Status, } = this.props; const innerProps: InnerComponentProps = { status, diff --git a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap index af767dc6a2..675b786243 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap +++ b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap @@ -43,6 +43,8 @@ exports[`VoiceSearch Rendering with custom template for status 1`] = ` exports[`VoiceSearch Rendering with default props 1`] = `

Date: Wed, 15 May 2019 15:04:02 +0200 Subject: [PATCH 45/66] fix(types): improve types for voiceSearch --- .../src/components/VoiceSearch.tsx | 4 +- .../lib/voiceSearchHelper/__tests__/index.ts | 81 ++++++++++--------- .../src/lib/voiceSearchHelper/index.ts | 17 ++-- 3 files changed, 52 insertions(+), 50 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 7a5c87ab6f..ba9502e6dc 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -11,8 +11,8 @@ type InnerComponentProps = { status: string; errorCode?: string; isListening: boolean; - transcript?: string; - isSpeechFinal?: boolean; + transcript: string; + isSpeechFinal: boolean; isBrowserSupported: boolean; }; diff --git a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts index dabddce65f..ce66ea463f 100644 --- a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts +++ b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts @@ -1,12 +1,14 @@ -// copied from https://github.com/algolia/instantsearch.js/blob/e904ad689d8300b829aff928bbed8e4cdbe37b7b/src/lib/voiceSearchHelper/__tests__/index-test.ts +// copied from https://github.com/algolia/instantsearch.js/blob/e2717c1d46221e08d98f84e5d56ff70d5813d6bf/src/lib/voiceSearchHelper/__tests__/index-test.ts -import voiceSearchHelper, { +import createVoiceSearchHelper, { VoiceSearchHelper, VoiceSearchHelperParams, } from '..'; -const getHelper = (opts?: VoiceSearchHelperParams): VoiceSearchHelper => - voiceSearchHelper( +const getVoiceSearchHelper = ( + opts?: VoiceSearchHelperParams +): VoiceSearchHelper => + createVoiceSearchHelper( opts || { searchAsYouSpeak: false, onQueryChange: () => {}, @@ -29,35 +31,35 @@ describe('VoiceSearchHelper', () => { }); it('has initial state correctly', () => { - const helper = getHelper(); - expect(helper.getState()).toEqual({ + const voiceSearchHelper = getVoiceSearchHelper(); + expect(voiceSearchHelper.getState()).toEqual({ errorCode: undefined, - isSpeechFinal: undefined, + isSpeechFinal: false, status: 'initial', - transcript: undefined, + transcript: '', }); }); it('is not supported', () => { - const helper = getHelper(); - expect(helper.isBrowserSupported()).toBe(false); + const voiceSearchHelper = getVoiceSearchHelper(); + expect(voiceSearchHelper.isBrowserSupported()).toBe(false); }); it('is not listening', () => { - const helper = getHelper(); - expect(helper.isListening()).toBe(false); + const voiceSearchHelper = getVoiceSearchHelper(); + expect(voiceSearchHelper.isListening()).toBe(false); }); it('is supported with webkitSpeechRecognition', () => { window.webkitSpeechRecognition = () => {}; - const helper = getHelper(); - expect(helper.isBrowserSupported()).toBe(true); + const voiceSearchHelper = getVoiceSearchHelper(); + expect(voiceSearchHelper.isBrowserSupported()).toBe(true); }); it('is supported with SpeechRecognition', () => { window.SpeechRecognition = () => {}; - const helper = getHelper(); - expect(helper.isBrowserSupported()).toBe(true); + const voiceSearchHelper = getVoiceSearchHelper(); + expect(voiceSearchHelper.isBrowserSupported()).toBe(true); }); it('works with mock SpeechRecognition (searchAsYouSpeak:false)', () => { @@ -70,17 +72,17 @@ describe('VoiceSearchHelper', () => { })); const onQueryChange = jest.fn(); const onStateChange = jest.fn(); - const helper = getHelper({ + const voiceSearchHelper = getVoiceSearchHelper({ searchAsYouSpeak: false, onQueryChange, onStateChange, }); - const { getState } = helper; - helper.toggleListening(); + + voiceSearchHelper.toggleListening(); expect(onStateChange).toHaveBeenCalledTimes(1); - expect(getState().status).toEqual('askingPermission'); + expect(voiceSearchHelper.getState().status).toEqual('askingPermission'); recognition.onstart(); - expect(getState().status).toEqual('waiting'); + expect(voiceSearchHelper.getState().status).toEqual('waiting'); recognition.onresult({ results: [ (() => { @@ -94,13 +96,13 @@ describe('VoiceSearchHelper', () => { })(), ], }); - expect(getState().status).toEqual('recognizing'); - expect(getState().transcript).toEqual('Hello World'); - expect(getState().isSpeechFinal).toBe(true); + expect(voiceSearchHelper.getState().status).toEqual('recognizing'); + expect(voiceSearchHelper.getState().transcript).toEqual('Hello World'); + expect(voiceSearchHelper.getState().isSpeechFinal).toBe(true); expect(onQueryChange).toHaveBeenCalledTimes(0); recognition.onend(); expect(onQueryChange).toHaveBeenCalledWith('Hello World'); - expect(getState().status).toEqual('finished'); + expect(voiceSearchHelper.getState().status).toEqual('finished'); }); it('works with mock SpeechRecognition (searchAsYouSpeak:true)', () => { @@ -113,17 +115,17 @@ describe('VoiceSearchHelper', () => { })); const onQueryChange = jest.fn(); const onStateChange = jest.fn(); - const helper = getHelper({ + const voiceSearchHelper = getVoiceSearchHelper({ searchAsYouSpeak: true, onQueryChange, onStateChange, }); - const { getState } = helper; - helper.toggleListening(); + + voiceSearchHelper.toggleListening(); expect(onStateChange).toHaveBeenCalledTimes(1); - expect(getState().status).toEqual('askingPermission'); + expect(voiceSearchHelper.getState().status).toEqual('askingPermission'); recognition.onstart(); - expect(getState().status).toEqual('waiting'); + expect(voiceSearchHelper.getState().status).toEqual('waiting'); recognition.onresult({ results: [ (() => { @@ -137,13 +139,13 @@ describe('VoiceSearchHelper', () => { })(), ], }); - expect(getState().status).toEqual('recognizing'); - expect(getState().transcript).toEqual('Hello World'); - expect(getState().isSpeechFinal).toBe(true); + expect(voiceSearchHelper.getState().status).toEqual('recognizing'); + expect(voiceSearchHelper.getState().transcript).toEqual('Hello World'); + expect(voiceSearchHelper.getState().isSpeechFinal).toBe(true); expect(onQueryChange).toHaveBeenCalledWith('Hello World'); recognition.onend(); expect(onQueryChange).toHaveBeenCalledTimes(1); - expect(getState().status).toEqual('finished'); + expect(voiceSearchHelper.getState().status).toEqual('finished'); }); it('works with onerror', () => { @@ -156,19 +158,18 @@ describe('VoiceSearchHelper', () => { })); const onQueryChange = jest.fn(); const onStateChange = jest.fn(); - const helper = getHelper({ + const voiceSearchHelper = getVoiceSearchHelper({ searchAsYouSpeak: true, onQueryChange, onStateChange, }); - const { getState } = helper; - helper.toggleListening(); - expect(getState().status).toEqual('askingPermission'); + voiceSearchHelper.toggleListening(); + expect(voiceSearchHelper.getState().status).toEqual('askingPermission'); recognition.onerror({ error: 'not-allowed', }); - expect(getState().status).toEqual('error'); - expect(getState().errorCode).toEqual('not-allowed'); + expect(voiceSearchHelper.getState().status).toEqual('error'); + expect(voiceSearchHelper.getState().errorCode).toEqual('not-allowed'); recognition.onend(); expect(onQueryChange).toHaveBeenCalledTimes(0); }); diff --git a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts index 800cd43cc0..abc6d68f78 100644 --- a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts @@ -1,4 +1,4 @@ -// copied from https://github.com/algolia/instantsearch.js/blob/e904ad689d8300b829aff928bbed8e4cdbe37b7b/src/lib/voiceSearchHelper/index.ts +// copied from https://github.com/algolia/instantsearch.js/blob/9788d816a7f2a979f75ffae81791c7d41361f772/src/lib/voiceSearchHelper/index.ts const STATUS_INITIAL = 'initial'; const STATUS_ASKING_PERMISSION = 'askingPermission'; @@ -15,8 +15,8 @@ export type VoiceSearchHelperParams = { export type VoiceListeningState = { status: string; - transcript?: string; - isSpeechFinal?: boolean; + transcript: string; + isSpeechFinal: boolean; errorCode?: string; }; @@ -39,8 +39,8 @@ export default function voiceSearchHelper({ (window as any).SpeechRecognition; const getDefaultState = (status: string): VoiceListeningState => ({ status, - transcript: undefined, - isSpeechFinal: undefined, + transcript: '', + isSpeechFinal: false, errorCode: undefined, }); let state: VoiceListeningState = getDefaultState(STATUS_INITIAL); @@ -91,9 +91,10 @@ export default function voiceSearchHelper({ setState({ status: STATUS_RECOGNIZING, transcript: - event.results[0] && - event.results[0][0] && - event.results[0][0].transcript, + (event.results[0] && + event.results[0][0] && + event.results[0][0].transcript) || + '', isSpeechFinal: event.results[0] && event.results[0].isFinal, }); if (searchAsYouSpeak && state.transcript) { From 4d17a689890d12580ea52acd0e251c882c147953 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Wed, 15 May 2019 16:41:40 +0200 Subject: [PATCH 46/66] test(voiceSearch): improve test --- .../src/components/__tests__/VoiceSearch.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx index 53b5d03fc8..57d0b130d8 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx @@ -54,12 +54,13 @@ describe('VoiceSearch', () => { mockIsListening.mockClear(); }); - it('renders a specific title when it is disabled', () => { + it('renders a disabled button when the browser is not supported', () => { mockIsBrowserSupported.mockImplementation(() => false); const wrapper = mount(); expect(wrapper.find('button').prop('title')).toBe( 'Search by voice (not supported on this browser)' ); + expect(wrapper.find('button').prop('disabled')).toBe(true); mockIsBrowserSupported.mockImplementation(() => true); }); From dbca103abb105654c51992516a0970281f1d5b9f Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Wed, 15 May 2019 16:45:24 +0200 Subject: [PATCH 47/66] fix(voiceSearch): rename buttonComponent to buttonTextComponent --- .../src/components/VoiceSearch.tsx | 10 +++++----- .../src/components/__tests__/VoiceSearch.tsx | 12 ++++++++---- .../__tests__/__snapshots__/VoiceSearch.tsx.snap | 6 +++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index ba9502e6dc..68c037eb7a 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -20,11 +20,11 @@ type VoiceSearchProps = { searchAsYouSpeak?: boolean; refine: (query: string) => void; translate: Translate; - buttonComponent: React.FC; + buttonTextComponent: React.FC; statusComponent: React.FC; }; -const DefaultButton: React.FC = ({ +const DefaultButtonText: React.FC = ({ status, errorCode, isListening, @@ -76,7 +76,7 @@ const DefaultStatus: React.FC = ({ transcript }) => ( class VoiceSearch extends Component { static defaultProps = { - buttonComponent: DefaultButton, + buttonTextComponent: DefaultButtonText, statusComponent: DefaultStatus, }; private voiceSearch: VoiceSearchHelper; @@ -99,7 +99,7 @@ class VoiceSearch extends Component { const { isListening, isBrowserSupported } = this.voiceSearch; const { translate, - buttonComponent: Button, + buttonTextComponent: ButtonText, statusComponent: Status, } = this.props; const innerProps: InnerComponentProps = { @@ -124,7 +124,7 @@ class VoiceSearch extends Component { onClick={this.onClick} disabled={!isBrowserSupported()} > -
diff --git a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx index 57d0b130d8..36c365c3ea 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx @@ -37,19 +37,23 @@ describe('VoiceSearch', () => { }); it('with custom component for button with isListening: false', () => { - const customButton = ({ isListening }) => + const customButtonText = ({ isListening }) => isListening ? 'Stop' : 'Start'; - const wrapper = mount(); + const wrapper = mount( + + ); expect(wrapper.find('button').text()).toBe('Start'); }); it('with custom component for button with isListening: true', () => { - const customButton = ({ isListening }) => + const customButtonText = ({ isListening }) => isListening ? 'Stop' : 'Start'; mockIsListening.mockImplementation(() => true); - const wrapper = mount(); + const wrapper = mount( + + ); expect(wrapper.find('button').text()).toBe('Stop'); mockIsListening.mockClear(); }); diff --git a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap index 675b786243..8e27303fd6 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap +++ b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap @@ -43,7 +43,7 @@ exports[`VoiceSearch Rendering with custom template for status 1`] = ` exports[`VoiceSearch Rendering with default props 1`] = ` @@ -57,7 +57,7 @@ exports[`VoiceSearch Rendering with default props 1`] = ` title="Search by voice" type="button" > - - +
Date: Wed, 15 May 2019 16:46:40 +0200 Subject: [PATCH 48/66] chore(voiceSearch): change the path to import directly from source --- stories/VoiceSearch.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/VoiceSearch.stories.tsx b/stories/VoiceSearch.stories.tsx index f96be03169..c558f5afa8 100644 --- a/stories/VoiceSearch.stories.tsx +++ b/stories/VoiceSearch.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; -import { VoiceSearch, SearchBox } from 'react-instantsearch-dom'; +import { VoiceSearch, SearchBox } from '../packages/react-instantsearch-dom'; import { WrapWithHits } from './util'; const stories = storiesOf('VoiceSearch', module); From 49a1aaa43a0358a63791aca55f57dd4ab032c9c4 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Wed, 15 May 2019 18:28:20 +0200 Subject: [PATCH 49/66] type(voiceSearch): add accessibility modifier to defaultProps --- packages/react-instantsearch-dom/src/components/VoiceSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 68c037eb7a..c50343dc09 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -75,7 +75,7 @@ const DefaultStatus: React.FC = ({ transcript }) => ( ); class VoiceSearch extends Component { - static defaultProps = { + protected static defaultProps = { buttonTextComponent: DefaultButtonText, statusComponent: DefaultStatus, }; From 53db919c7949237596de842671c9416defe29ab6 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Thu, 16 May 2019 11:22:16 +0200 Subject: [PATCH 50/66] test(voiceSearch): extract styles from stories to util.css --- stories/VoiceSearch.stories.tsx | 65 +++++++-------------------------- storybook/public/util.css | 31 ++++++++++++++++ 2 files changed, 44 insertions(+), 52 deletions(-) diff --git a/stories/VoiceSearch.stories.tsx b/stories/VoiceSearch.stories.tsx index c558f5afa8..7ae45322fd 100644 --- a/stories/VoiceSearch.stories.tsx +++ b/stories/VoiceSearch.stories.tsx @@ -38,28 +38,19 @@ stories )) - .add('with a custom button text', () => { - const style = window.document.createElement('style'); - window.document.head.appendChild(style); - [ - `.custom-button-story .ais-VoiceSearch-button:hover { - background: inherit; - }`, - ].forEach(rule => (style.sheet as CSSStyleSheet).insertRule(rule)); - return ( - -
- (isListening ? '⏹' : '🎙')} - /> -
-
- ); - }) + .add('with a custom button text', () => ( + +
+ (isListening ? '⏹' : '🎙')} + /> +
+
+ )) .add('with full status', () => { const Status = ({ status, @@ -122,36 +113,6 @@ stories ); }) .add('example of dynamic UI working with SearchBox', () => { - const style = window.document.createElement('style'); - window.document.head.appendChild(style); - [ - `.custom-ui .ais-VoiceSearch-button { - position: absolute; - right: 43px; - top: 53px; - z-index: 3; - }`, - `.custom-ui .ais-VoiceSearch-status .layer { - position: absolute; - background: rgba(255, 255, 255, 0.95); - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: 2; - align-items: center; - justify-content: center; - display: none; - }`, - `.custom-ui .ais-VoiceSearch-status .layer.listening-true { - display: flex; - }`, - `.custom-ui .ais-VoiceSearch-status .layer span { - font-size: 2rem; - color: #555; - }`, - ].forEach(rule => (style.sheet as CSSStyleSheet).insertRule(rule)); - const Status = ({ isListening, transcript }) => { return (
diff --git a/storybook/public/util.css b/storybook/public/util.css index 206fd9e42c..fd737396df 100644 --- a/storybook/public/util.css +++ b/storybook/public/util.css @@ -191,3 +191,34 @@ .my-custom-marker--active::after { background-color: #3369e7; } + +/* Voice Search */ +.custom-button-story .ais-VoiceSearch-button:hover { + background: inherit; +} + +.custom-ui .ais-VoiceSearch-button { + position: absolute; + right: 43px; + top: 53px; + z-index: 3; +} +.custom-ui .ais-VoiceSearch-status .layer { + position: absolute; + background: rgba(255, 255, 255, 0.95); + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 2; + align-items: center; + justify-content: center; + display: none; +} +.custom-ui .ais-VoiceSearch-status .layer.listening-true { + display: flex; +} +.custom-ui .ais-VoiceSearch-status .layer span { + font-size: 2rem; + color: #555; +} From de79d8db76a484b3cd3e405af16151fcc15e9ab0 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Thu, 16 May 2019 14:17:52 +0200 Subject: [PATCH 51/66] test(voiceSearch): clean up mocks after each test --- .../src/components/__tests__/VoiceSearch.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx index 36c365c3ea..21cd23df1d 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx @@ -22,6 +22,13 @@ jest.mock('../../lib/voiceSearchHelper', () => { Enzyme.configure({ adapter: new Adapter() }); describe('VoiceSearch', () => { + afterEach(() => { + mockGetState.mockClear(); + mockIsBrowserSupported.mockImplementation(() => true); + mockIsListening.mockClear(); + mockToggleListening.mockClear(); + }); + describe('button', () => { it('calls toggleListening when button is clicked', () => { const wrapper = mount(); @@ -55,7 +62,6 @@ describe('VoiceSearch', () => { ); expect(wrapper.find('button').text()).toBe('Stop'); - mockIsListening.mockClear(); }); it('renders a disabled button when the browser is not supported', () => { @@ -65,7 +71,6 @@ describe('VoiceSearch', () => { 'Search by voice (not supported on this browser)' ); expect(wrapper.find('button').prop('disabled')).toBe(true); - mockIsBrowserSupported.mockImplementation(() => true); }); it('with custom template for status', () => { @@ -97,8 +102,6 @@ describe('VoiceSearch', () => { const wrapper = mount(); expect(wrapper.find('.ais-VoiceSearch-status')).toMatchSnapshot(); - mockIsListening.mockClear(); - mockGetState.mockClear(); }); }); }); From 8f8e971714ec61e24640311ee4b76a4ebdc6b5df Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Thu, 16 May 2019 14:59:36 +0200 Subject: [PATCH 52/66] feat(voiceSearch): add dispose to voiceSearchHelper --- .../lib/voiceSearchHelper/__tests__/index.ts | 132 ++++++++++-------- .../src/lib/voiceSearchHelper/index.ts | 87 +++++++----- 2 files changed, 130 insertions(+), 89 deletions(-) diff --git a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts index ce66ea463f..a41cbd873a 100644 --- a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts +++ b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/__tests__/index.ts @@ -1,20 +1,6 @@ -// copied from https://github.com/algolia/instantsearch.js/blob/e2717c1d46221e08d98f84e5d56ff70d5813d6bf/src/lib/voiceSearchHelper/__tests__/index-test.ts - -import createVoiceSearchHelper, { - VoiceSearchHelper, - VoiceSearchHelperParams, -} from '..'; - -const getVoiceSearchHelper = ( - opts?: VoiceSearchHelperParams -): VoiceSearchHelper => - createVoiceSearchHelper( - opts || { - searchAsYouSpeak: false, - onQueryChange: () => {}, - onStateChange: () => {}, - } - ); +// copied from https://github.com/algolia/instantsearch.js/blob/0e988cc85487f61aa3b61131c22bed135ddfd76d/src/lib/voiceSearchHelper/__tests__/index-test.ts + +import createVoiceSearchHelper from '..'; type DummySpeechRecognition = () => void; declare global { @@ -24,14 +10,35 @@ declare global { } } +const start = jest.fn(); +const stop = jest.fn(); + +const createFakeSpeechRecognition = (): jest.Mock => { + const simulateListener: any = {}; + const mock = jest.fn().mockImplementation(() => ({ + start, + stop, + addEventListener(eventName: string, callback: () => void) { + simulateListener[eventName] = callback; + }, + removeEventListener() {}, + })); + (mock as any).simulateListener = simulateListener; + return mock; +}; + describe('VoiceSearchHelper', () => { - beforeEach(() => { + afterEach(() => { delete window.webkitSpeechRecognition; delete window.SpeechRecognition; }); it('has initial state correctly', () => { - const voiceSearchHelper = getVoiceSearchHelper(); + const voiceSearchHelper = createVoiceSearchHelper({ + searchAsYouSpeak: false, + onQueryChange: () => {}, + onStateChange: () => {}, + }); expect(voiceSearchHelper.getState()).toEqual({ errorCode: undefined, isSpeechFinal: false, @@ -41,38 +48,49 @@ describe('VoiceSearchHelper', () => { }); it('is not supported', () => { - const voiceSearchHelper = getVoiceSearchHelper(); + const voiceSearchHelper = createVoiceSearchHelper({ + searchAsYouSpeak: false, + onQueryChange: () => {}, + onStateChange: () => {}, + }); expect(voiceSearchHelper.isBrowserSupported()).toBe(false); }); it('is not listening', () => { - const voiceSearchHelper = getVoiceSearchHelper(); + const voiceSearchHelper = createVoiceSearchHelper({ + searchAsYouSpeak: false, + onQueryChange: () => {}, + onStateChange: () => {}, + }); expect(voiceSearchHelper.isListening()).toBe(false); }); it('is supported with webkitSpeechRecognition', () => { window.webkitSpeechRecognition = () => {}; - const voiceSearchHelper = getVoiceSearchHelper(); + const voiceSearchHelper = createVoiceSearchHelper({ + searchAsYouSpeak: false, + onQueryChange: () => {}, + onStateChange: () => {}, + }); expect(voiceSearchHelper.isBrowserSupported()).toBe(true); }); it('is supported with SpeechRecognition', () => { - window.SpeechRecognition = () => {}; - const voiceSearchHelper = getVoiceSearchHelper(); + window.SpeechRecognition = createFakeSpeechRecognition(); + const voiceSearchHelper = createVoiceSearchHelper({ + searchAsYouSpeak: false, + onQueryChange: () => {}, + onStateChange: () => {}, + }); expect(voiceSearchHelper.isBrowserSupported()).toBe(true); }); it('works with mock SpeechRecognition (searchAsYouSpeak:false)', () => { - let recognition; - window.SpeechRecognition = jest.fn().mockImplementation(() => ({ - start() { - /* eslint-disable-next-line consistent-this */ - recognition = this; - }, - })); + window.SpeechRecognition = createFakeSpeechRecognition(); + const { simulateListener } = window.SpeechRecognition as any; const onQueryChange = jest.fn(); const onStateChange = jest.fn(); - const voiceSearchHelper = getVoiceSearchHelper({ + const voiceSearchHelper = createVoiceSearchHelper({ searchAsYouSpeak: false, onQueryChange, onStateChange, @@ -81,9 +99,9 @@ describe('VoiceSearchHelper', () => { voiceSearchHelper.toggleListening(); expect(onStateChange).toHaveBeenCalledTimes(1); expect(voiceSearchHelper.getState().status).toEqual('askingPermission'); - recognition.onstart(); + simulateListener.start(); expect(voiceSearchHelper.getState().status).toEqual('waiting'); - recognition.onresult({ + simulateListener.result({ results: [ (() => { const obj = [ @@ -100,22 +118,17 @@ describe('VoiceSearchHelper', () => { expect(voiceSearchHelper.getState().transcript).toEqual('Hello World'); expect(voiceSearchHelper.getState().isSpeechFinal).toBe(true); expect(onQueryChange).toHaveBeenCalledTimes(0); - recognition.onend(); + simulateListener.end(); expect(onQueryChange).toHaveBeenCalledWith('Hello World'); expect(voiceSearchHelper.getState().status).toEqual('finished'); }); it('works with mock SpeechRecognition (searchAsYouSpeak:true)', () => { - let recognition; - window.SpeechRecognition = jest.fn().mockImplementation(() => ({ - start() { - /* eslint-disable-next-line consistent-this */ - recognition = this; - }, - })); + window.SpeechRecognition = createFakeSpeechRecognition(); + const { simulateListener } = window.SpeechRecognition as any; const onQueryChange = jest.fn(); const onStateChange = jest.fn(); - const voiceSearchHelper = getVoiceSearchHelper({ + const voiceSearchHelper = createVoiceSearchHelper({ searchAsYouSpeak: true, onQueryChange, onStateChange, @@ -124,9 +137,9 @@ describe('VoiceSearchHelper', () => { voiceSearchHelper.toggleListening(); expect(onStateChange).toHaveBeenCalledTimes(1); expect(voiceSearchHelper.getState().status).toEqual('askingPermission'); - recognition.onstart(); + simulateListener.start(); expect(voiceSearchHelper.getState().status).toEqual('waiting'); - recognition.onresult({ + simulateListener.result({ results: [ (() => { const obj = [ @@ -143,34 +156,41 @@ describe('VoiceSearchHelper', () => { expect(voiceSearchHelper.getState().transcript).toEqual('Hello World'); expect(voiceSearchHelper.getState().isSpeechFinal).toBe(true); expect(onQueryChange).toHaveBeenCalledWith('Hello World'); - recognition.onend(); + simulateListener.end(); expect(onQueryChange).toHaveBeenCalledTimes(1); expect(voiceSearchHelper.getState().status).toEqual('finished'); }); it('works with onerror', () => { - let recognition; - window.SpeechRecognition = jest.fn().mockImplementation(() => ({ - start() { - /* eslint-disable-next-line consistent-this */ - recognition = this; - }, - })); + window.SpeechRecognition = createFakeSpeechRecognition(); + const { simulateListener } = window.SpeechRecognition as any; const onQueryChange = jest.fn(); const onStateChange = jest.fn(); - const voiceSearchHelper = getVoiceSearchHelper({ + const voiceSearchHelper = createVoiceSearchHelper({ searchAsYouSpeak: true, onQueryChange, onStateChange, }); voiceSearchHelper.toggleListening(); expect(voiceSearchHelper.getState().status).toEqual('askingPermission'); - recognition.onerror({ + simulateListener.error({ error: 'not-allowed', }); expect(voiceSearchHelper.getState().status).toEqual('error'); expect(voiceSearchHelper.getState().errorCode).toEqual('not-allowed'); - recognition.onend(); + simulateListener.end(); expect(onQueryChange).toHaveBeenCalledTimes(0); }); + + it('stops listening on `dispose`', () => { + window.SpeechRecognition = createFakeSpeechRecognition(); + const voiceSearchHelper = createVoiceSearchHelper({ + searchAsYouSpeak: false, + onQueryChange: () => {}, + onStateChange: () => {}, + }); + voiceSearchHelper.toggleListening(); + voiceSearchHelper.dispose(); + expect(stop).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts index abc6d68f78..9ba55ecd93 100644 --- a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts @@ -1,4 +1,4 @@ -// copied from https://github.com/algolia/instantsearch.js/blob/9788d816a7f2a979f75ffae81791c7d41361f772/src/lib/voiceSearchHelper/index.ts +// copied from https://github.com/algolia/instantsearch.js/blob/0e988cc85487f61aa3b61131c22bed135ddfd76d/src/lib/voiceSearchHelper/index.ts const STATUS_INITIAL = 'initial'; const STATUS_ASKING_PERMISSION = 'askingPermission'; @@ -25,11 +25,12 @@ export type VoiceSearchHelper = { isBrowserSupported: () => boolean; isListening: () => boolean; toggleListening: () => void; + dispose: () => void; }; export type ToggleListening = () => void; -export default function voiceSearchHelper({ +export default function createVoiceSearchHelper({ searchAsYouSpeak, onQueryChange, onStateChange, @@ -64,6 +65,40 @@ export default function voiceSearchHelper({ setState(getDefaultState(status)); }; + const onStart = (): void => { + setState({ + status: STATUS_WAITING, + }); + }; + + const onError = (event: SpeechRecognitionError): void => { + setState({ status: STATUS_ERROR, errorCode: event.error }); + }; + + const onResult = (event: SpeechRecognitionEvent): void => { + setState({ + status: STATUS_RECOGNIZING, + transcript: + (event.results[0] && + event.results[0][0] && + event.results[0][0].transcript) || + '', + isSpeechFinal: event.results[0] && event.results[0].isFinal, + }); + if (searchAsYouSpeak && state.transcript) { + onQueryChange(state.transcript); + } + }; + + const onEnd = (): void => { + if (!state.errorCode && state.transcript && !searchAsYouSpeak) { + onQueryChange(state.transcript); + } + if (state.status !== STATUS_ERROR) { + setState({ status: STATUS_FINISHED }); + } + }; + const stop = (): void => { if (recognition) { recognition.stop(); @@ -79,40 +114,25 @@ export default function voiceSearchHelper({ } resetState(STATUS_ASKING_PERMISSION); recognition.interimResults = true; - recognition.onstart = () => { - setState({ - status: STATUS_WAITING, - }); - }; - recognition.onerror = (event: SpeechRecognitionError) => { - setState({ status: STATUS_ERROR, errorCode: event.error }); - }; - recognition.onresult = (event: SpeechRecognitionEvent) => { - setState({ - status: STATUS_RECOGNIZING, - transcript: - (event.results[0] && - event.results[0][0] && - event.results[0][0].transcript) || - '', - isSpeechFinal: event.results[0] && event.results[0].isFinal, - }); - if (searchAsYouSpeak && state.transcript) { - onQueryChange(state.transcript); - } - }; - recognition.onend = () => { - if (!state.errorCode && state.transcript && !searchAsYouSpeak) { - onQueryChange(state.transcript); - } - if (state.status !== STATUS_ERROR) { - setState({ status: STATUS_FINISHED }); - } - }; - + recognition.addEventListener('start', onStart); + recognition.addEventListener('error', onError); + recognition.addEventListener('result', onResult); + recognition.addEventListener('end', onEnd); recognition.start(); }; + const dispose = (): void => { + if (!recognition) { + return; + } + recognition.stop(); + recognition.removeEventListener('start', onStart); + recognition.removeEventListener('error', onError); + recognition.removeEventListener('result', onResult); + recognition.removeEventListener('end', onEnd); + recognition = undefined; + }; + const toggleListening = (): void => { if (!isBrowserSupported()) { return; @@ -129,5 +149,6 @@ export default function voiceSearchHelper({ isBrowserSupported, isListening, toggleListening, + dispose, }; } From 0d230822a44ab48415de20825735ed7293e8c1a1 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Thu, 16 May 2019 15:02:18 +0200 Subject: [PATCH 53/66] chore(voiceSearch): rename voiceSearchHelper to createVoiceSearchHelper --- .../src/components/VoiceSearch.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index c50343dc09..8eefe311a9 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { translatable, Translate } from 'react-instantsearch-core'; import { createClassNames } from '../core/utils'; -import voiceSearchHelper, { +import createVoiceSearchHelper, { VoiceSearchHelper, VoiceListeningState, } from '../lib/voiceSearchHelper'; @@ -79,24 +79,24 @@ class VoiceSearch extends Component { buttonTextComponent: DefaultButtonText, statusComponent: DefaultStatus, }; - private voiceSearch: VoiceSearchHelper; + private voiceSearchHelper: VoiceSearchHelper; constructor(props: VoiceSearchProps) { super(props); const { searchAsYouSpeak = false, refine } = props; - this.voiceSearch = voiceSearchHelper({ + this.voiceSearchHelper = createVoiceSearchHelper({ searchAsYouSpeak, onQueryChange: query => refine(query), onStateChange: () => { - this.setState(this.voiceSearch.getState()); + this.setState(this.voiceSearchHelper.getState()); }, }); - this.state = this.voiceSearch.getState(); + this.state = this.voiceSearchHelper.getState(); } public render() { const { status, transcript, isSpeechFinal, errorCode } = this.state; - const { isListening, isBrowserSupported } = this.voiceSearch; + const { isListening, isBrowserSupported } = this.voiceSearchHelper; const { translate, buttonTextComponent: ButtonText, @@ -135,7 +135,7 @@ class VoiceSearch extends Component { private onClick = (event: React.MouseEvent) => { event.currentTarget.blur(); - const { toggleListening } = this.voiceSearch; + const { toggleListening } = this.voiceSearchHelper; toggleListening(); }; } From 92cf94f9bd44939dda23a94dc86a263de80e7e0b Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Thu, 16 May 2019 15:07:37 +0200 Subject: [PATCH 54/66] feat(voiceSearch): dispose voiceSearchHelper on unmount --- .../src/components/VoiceSearch.tsx | 4 ++++ .../src/components/__tests__/VoiceSearch.tsx | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 8eefe311a9..76705a0b16 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -133,6 +133,10 @@ class VoiceSearch extends Component { ); } + componentWillUnmount() { + this.voiceSearchHelper.dispose(); + } + private onClick = (event: React.MouseEvent) => { event.currentTarget.blur(); const { toggleListening } = this.voiceSearchHelper; diff --git a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx index 21cd23df1d..2baa66eeeb 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx @@ -7,6 +7,7 @@ const mockGetState = jest.fn().mockImplementation(() => ({})); const mockIsBrowserSupported = jest.fn().mockImplementation(() => true); const mockIsListening = jest.fn(); const mockToggleListening = jest.fn(); +const mockDispose = jest.fn(); jest.mock('../../lib/voiceSearchHelper', () => { return () => { @@ -15,6 +16,7 @@ jest.mock('../../lib/voiceSearchHelper', () => { isBrowserSupported: mockIsBrowserSupported, isListening: mockIsListening, toggleListening: mockToggleListening, + dispose: mockDispose, }; }; }); @@ -103,5 +105,12 @@ describe('VoiceSearch', () => { const wrapper = mount(); expect(wrapper.find('.ais-VoiceSearch-status')).toMatchSnapshot(); }); + + it('calls voiceSearchHelper.dispose() on unmount', () => { + const wrapper = mount(); + wrapper.find('button').simulate('click'); + wrapper.unmount(); + expect(mockDispose).toHaveBeenCalledTimes(1); + }); }); }); From 48d6dc4e4b479ad8fc418183112e284f91c9484d Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Thu, 16 May 2019 16:02:29 +0200 Subject: [PATCH 55/66] chore(voiceSearch): extract svg element to a component to remove duplicates --- .../src/components/VoiceSearch.tsx | 44 ++++++------- .../__snapshots__/VoiceSearch.tsx.snap | 64 ++++++++++--------- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 76705a0b16..603c83bb8c 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -24,41 +24,37 @@ type VoiceSearchProps = { statusComponent: React.FC; }; +const ButtonSvg = ({ children }) => ( + + {children} + +); + const DefaultButtonText: React.FC = ({ status, errorCode, isListening, }) => { return status === 'error' && errorCode === 'not-allowed' ? ( - + - + ) : ( - + = ({ - + ); }; diff --git a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap index 8e27303fd6..c87ad1f4d2 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap +++ b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap @@ -60,37 +60,39 @@ exports[`VoiceSearch Rendering with default props 1`] = ` - - - - - - + + + + + + + +
Date: Thu, 16 May 2019 16:23:35 +0200 Subject: [PATCH 56/66] type(voiceSearch): add accessibility modifier --- packages/react-instantsearch-dom/src/components/VoiceSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 603c83bb8c..199a80607e 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -129,7 +129,7 @@ class VoiceSearch extends Component { ); } - componentWillUnmount() { + public componentWillUnmount() { this.voiceSearchHelper.dispose(); } From 1df924bc8c01a1cf99f715e8cc308b47f2a6bf3b Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Thu, 16 May 2019 16:43:10 +0200 Subject: [PATCH 57/66] chore: update bundlesize --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b0ce2f744e..ec1b28eb90 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ }, { "path": "packages/react-instantsearch/dist/umd/Dom.min.js", - "maxSize": "64.10 kB" + "maxSize": "64.20 kB" }, { "path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js", @@ -142,7 +142,7 @@ }, { "path": "packages/react-instantsearch-dom/dist/umd/ReactInstantSearchDOM.min.js", - "maxSize": "63.90 kB" + "maxSize": "63.95 kB" }, { "path": "packages/react-instantsearch-dom-maps/dist/umd/ReactInstantSearchDOMMaps.min.js", From 643cf348795ebf6841a74e4bfd97e45b02058497 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Thu, 16 May 2019 17:07:44 +0200 Subject: [PATCH 58/66] chore: adjust bundlesize --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ec1b28eb90..90f5607037 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,7 @@ }, { "path": "packages/react-instantsearch-dom/dist/umd/ReactInstantSearchDOM.min.js", - "maxSize": "63.95 kB" + "maxSize": "64.00 kB" }, { "path": "packages/react-instantsearch-dom-maps/dist/umd/ReactInstantSearchDOMMaps.min.js", From c1a3a31a1ee2f4ffe40735a68a4d3f3786819865 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 27 May 2019 17:56:55 +0200 Subject: [PATCH 59/66] test(voiceSearch): fix wrong cleanup --- .../src/components/__tests__/VoiceSearch.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx index 2baa66eeeb..7b6b138f93 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/__tests__/VoiceSearch.tsx @@ -25,7 +25,7 @@ Enzyme.configure({ adapter: new Adapter() }); describe('VoiceSearch', () => { afterEach(() => { - mockGetState.mockClear(); + mockGetState.mockImplementation(() => ({})); mockIsBrowserSupported.mockImplementation(() => true); mockIsListening.mockClear(); mockToggleListening.mockClear(); From b93918ad5132562f9e530ae10acb299c514fffde Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Mon, 27 May 2019 18:05:07 +0200 Subject: [PATCH 60/66] fix(voiceSearch): use defaultProps for searchAsYouSpeak --- .../react-instantsearch-dom/src/components/VoiceSearch.tsx | 5 +++-- .../components/__tests__/__snapshots__/VoiceSearch.tsx.snap | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 199a80607e..9219bcd132 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -17,7 +17,7 @@ type InnerComponentProps = { }; type VoiceSearchProps = { - searchAsYouSpeak?: boolean; + searchAsYouSpeak: boolean; refine: (query: string) => void; translate: Translate; buttonTextComponent: React.FC; @@ -72,6 +72,7 @@ const DefaultStatus: React.FC = ({ transcript }) => ( class VoiceSearch extends Component { protected static defaultProps = { + searchAsYouSpeak: false, buttonTextComponent: DefaultButtonText, statusComponent: DefaultStatus, }; @@ -79,7 +80,7 @@ class VoiceSearch extends Component { constructor(props: VoiceSearchProps) { super(props); - const { searchAsYouSpeak = false, refine } = props; + const { searchAsYouSpeak, refine } = props; this.voiceSearchHelper = createVoiceSearchHelper({ searchAsYouSpeak, onQueryChange: query => refine(query), diff --git a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap index c87ad1f4d2..6d9e612fa0 100644 --- a/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap +++ b/packages/react-instantsearch-dom/src/components/__tests__/__snapshots__/VoiceSearch.tsx.snap @@ -44,6 +44,7 @@ exports[`VoiceSearch Rendering with default props 1`] = ` From 11bbf8ba33599f801f73c3cd4ae913d56236a9cc Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 28 May 2019 12:44:49 +0200 Subject: [PATCH 61/66] fix(types): introduce Status, ErrorCode types --- .../src/components/VoiceSearch.tsx | 14 +++-- .../src/lib/voiceSearchHelper/index.ts | 55 +++++++++++-------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index 9219bcd132..db0d6d1c5b 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -4,12 +4,14 @@ import { createClassNames } from '../core/utils'; import createVoiceSearchHelper, { VoiceSearchHelper, VoiceListeningState, + Status, + ErrorCode, } from '../lib/voiceSearchHelper'; const cx = createClassNames('VoiceSearch'); type InnerComponentProps = { - status: string; - errorCode?: string; + status: Status; + errorCode?: ErrorCode; isListening: boolean; transcript: string; isSpeechFinal: boolean; @@ -96,8 +98,8 @@ class VoiceSearch extends Component { const { isListening, isBrowserSupported } = this.voiceSearchHelper; const { translate, - buttonTextComponent: ButtonText, - statusComponent: Status, + buttonTextComponent: ButtonTextComponent, + statusComponent: StatusComponent, } = this.props; const innerProps: InnerComponentProps = { status, @@ -121,10 +123,10 @@ class VoiceSearch extends Component { onClick={this.onClick} disabled={!isBrowserSupported()} > - +
- +
); diff --git a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts index 9ba55ecd93..d346fcf015 100644 --- a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts @@ -1,23 +1,34 @@ // copied from https://github.com/algolia/instantsearch.js/blob/0e988cc85487f61aa3b61131c22bed135ddfd76d/src/lib/voiceSearchHelper/index.ts -const STATUS_INITIAL = 'initial'; -const STATUS_ASKING_PERMISSION = 'askingPermission'; -const STATUS_WAITING = 'waiting'; -const STATUS_RECOGNIZING = 'recognizing'; -const STATUS_FINISHED = 'finished'; -const STATUS_ERROR = 'error'; - export type VoiceSearchHelperParams = { searchAsYouSpeak: boolean; onQueryChange: (query: string) => void; onStateChange: () => void; }; +export type Status = + | 'initial' + | 'askingPermission' + | 'waiting' + | 'recognizing' + | 'finished' + | 'error'; + +export type ErrorCode = + | 'no-speech' + | 'aborted' + | 'audio-capture' + | 'network' + | 'not-allowed' + | 'service-not-allowed' + | 'bad-grammar' + | 'language-not-supported'; + export type VoiceListeningState = { - status: string; + status: Status; transcript: string; isSpeechFinal: boolean; - errorCode?: string; + errorCode?: ErrorCode; }; export type VoiceSearchHelper = { @@ -38,46 +49,46 @@ export default function createVoiceSearchHelper({ const SpeechRecognitionAPI: new () => SpeechRecognition = (window as any).webkitSpeechRecognition || (window as any).SpeechRecognition; - const getDefaultState = (status: string): VoiceListeningState => ({ + const getDefaultState = (status: Status): VoiceListeningState => ({ status, transcript: '', isSpeechFinal: false, errorCode: undefined, }); - let state: VoiceListeningState = getDefaultState(STATUS_INITIAL); + let state: VoiceListeningState = getDefaultState('initial'); let recognition: SpeechRecognition | undefined; const isBrowserSupported = (): boolean => Boolean(SpeechRecognitionAPI); const isListening = (): boolean => - state.status === STATUS_ASKING_PERMISSION || - state.status === STATUS_WAITING || - state.status === STATUS_RECOGNIZING; + state.status === 'askingPermission' || + state.status === 'waiting' || + state.status === 'recognizing'; - const setState = (newState = {}): void => { + const setState = (newState: Partial = {}): void => { state = { ...state, ...newState }; onStateChange(); }; const getState = (): VoiceListeningState => state; - const resetState = (status = STATUS_INITIAL): void => { + const resetState = (status: Status = 'initial'): void => { setState(getDefaultState(status)); }; const onStart = (): void => { setState({ - status: STATUS_WAITING, + status: 'waiting', }); }; const onError = (event: SpeechRecognitionError): void => { - setState({ status: STATUS_ERROR, errorCode: event.error }); + setState({ status: 'error', errorCode: event.error }); }; const onResult = (event: SpeechRecognitionEvent): void => { setState({ - status: STATUS_RECOGNIZING, + status: 'recognizing', transcript: (event.results[0] && event.results[0][0] && @@ -94,8 +105,8 @@ export default function createVoiceSearchHelper({ if (!state.errorCode && state.transcript && !searchAsYouSpeak) { onQueryChange(state.transcript); } - if (state.status !== STATUS_ERROR) { - setState({ status: STATUS_FINISHED }); + if (state.status !== 'error') { + setState({ status: 'finished' }); } }; @@ -112,7 +123,7 @@ export default function createVoiceSearchHelper({ if (!recognition) { return; } - resetState(STATUS_ASKING_PERMISSION); + resetState('askingPermission'); recognition.interimResults = true; recognition.addEventListener('start', onStart); recognition.addEventListener('error', onError); From 085d58fd8703fef432e684b8d261cfedfeba76ee Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 28 May 2019 15:57:04 +0200 Subject: [PATCH 62/66] fix(voiceSearch): initialize voiceSearchHelper at componentDidMount --- .../src/components/VoiceSearch.tsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx index db0d6d1c5b..1bcf82ee3c 100644 --- a/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx +++ b/packages/react-instantsearch-dom/src/components/VoiceSearch.tsx @@ -78,22 +78,25 @@ class VoiceSearch extends Component { buttonTextComponent: DefaultButtonText, statusComponent: DefaultStatus, }; - private voiceSearchHelper: VoiceSearchHelper; + private voiceSearchHelper?: VoiceSearchHelper; - constructor(props: VoiceSearchProps) { - super(props); - const { searchAsYouSpeak, refine } = props; + public componentDidMount() { + const { searchAsYouSpeak, refine } = this.props; this.voiceSearchHelper = createVoiceSearchHelper({ searchAsYouSpeak, onQueryChange: query => refine(query), onStateChange: () => { - this.setState(this.voiceSearchHelper.getState()); + this.setState(this.voiceSearchHelper!.getState()); }, }); - this.state = this.voiceSearchHelper.getState(); + this.setState(this.voiceSearchHelper.getState()); } public render() { + if (!this.voiceSearchHelper) { + return null; + } + const { status, transcript, isSpeechFinal, errorCode } = this.state; const { isListening, isBrowserSupported } = this.voiceSearchHelper; const { @@ -133,10 +136,15 @@ class VoiceSearch extends Component { } public componentWillUnmount() { - this.voiceSearchHelper.dispose(); + if (this.voiceSearchHelper) { + this.voiceSearchHelper.dispose(); + } } private onClick = (event: React.MouseEvent) => { + if (!this.voiceSearchHelper) { + return; + } event.currentTarget.blur(); const { toggleListening } = this.voiceSearchHelper; toggleListening(); From e5bb1ad822c7319be11316c4aeea244bd3699d40 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 28 May 2019 16:04:38 +0200 Subject: [PATCH 63/66] fix(voiceSearch): stopping voice recognition also removes event listeners --- .../src/lib/voiceSearchHelper/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts index d346fcf015..4a89155ca6 100644 --- a/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts +++ b/packages/react-instantsearch-dom/src/lib/voiceSearchHelper/index.ts @@ -111,10 +111,7 @@ export default function createVoiceSearchHelper({ }; const stop = (): void => { - if (recognition) { - recognition.stop(); - recognition = undefined; - } + dispose(); resetState(); }; From 6509e7c1208fd2ed3d9df7151a7fd6fd652c13d4 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Wed, 29 May 2019 16:15:10 +0200 Subject: [PATCH 64/66] chore(voiceSearch): rename index.js to index.ts and modify rollup config --- packages/react-instantsearch-core/rollup.config.js | 2 +- packages/react-instantsearch-core/src/{index.js => index.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/react-instantsearch-core/src/{index.js => index.ts} (100%) diff --git a/packages/react-instantsearch-core/rollup.config.js b/packages/react-instantsearch-core/rollup.config.js index 83c84d0728..50c5a7f2d0 100644 --- a/packages/react-instantsearch-core/rollup.config.js +++ b/packages/react-instantsearch-core/rollup.config.js @@ -38,7 +38,7 @@ const plugins = [ ]; const createConfiguration = ({ name, minify = false } = {}) => ({ - input: 'src/index.js', + input: 'src/index.ts', external: ['react'], output: { file: `dist/umd/ReactInstantSearch${name}${minify ? '.min' : ''}.js`, diff --git a/packages/react-instantsearch-core/src/index.js b/packages/react-instantsearch-core/src/index.ts similarity index 100% rename from packages/react-instantsearch-core/src/index.js rename to packages/react-instantsearch-core/src/index.ts From 9656c5a3f5b33b42de82b8add8a24b0504740ef6 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Wed, 29 May 2019 18:02:03 +0200 Subject: [PATCH 65/66] chore(voiceSearch): fix wrong prop name on story --- stories/VoiceSearch.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/VoiceSearch.stories.tsx b/stories/VoiceSearch.stories.tsx index 7ae45322fd..17266e1db6 100644 --- a/stories/VoiceSearch.stories.tsx +++ b/stories/VoiceSearch.stories.tsx @@ -46,7 +46,7 @@ stories >
(isListening ? '⏹' : '🎙')} + buttonTextComponent={({ isListening }) => (isListening ? '⏹' : '🎙')} />
From 4867016d9d3bdc86a6405475b02020ae3b589850 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Fri, 31 May 2019 13:55:24 +0200 Subject: [PATCH 66/66] feat(voiceSearch): export createVoiceSearchHelper for users to create custom component --- packages/react-instantsearch-dom/src/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/react-instantsearch-dom/src/index.js b/packages/react-instantsearch-dom/src/index.js index 9a19f97fa1..1b24c4ec6e 100644 --- a/packages/react-instantsearch-dom/src/index.js +++ b/packages/react-instantsearch-dom/src/index.js @@ -65,3 +65,6 @@ export { default as QueryRuleCustomData } from './widgets/QueryRuleCustomData'; // Utils export { createClassNames } from './core/utils'; + +// voiceSearchHelper +export { default as createVoiceSearchHelper } from './lib/voiceSearchHelper';