Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(voice): allow custom voice helper #4363

Merged
merged 11 commits into from
May 11, 2020
8 changes: 2 additions & 6 deletions src/components/VoiceSearch/VoiceSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import { h } from 'preact';
import Template from '../Template/Template';

import { VoiceSearchTemplates } from '../../widgets/voice-search/voice-search';

import {
VoiceListeningState,
ToggleListening,
} from '../../lib/voiceSearchHelper';
import { VoiceListeningState } from '../../lib/voiceSearchHelper/types';

export type VoiceSearchComponentCSSClasses = {
root: string;
Expand All @@ -20,7 +16,7 @@ export type VoiceSearchProps = {
cssClasses: VoiceSearchComponentCSSClasses;
isBrowserSupported: boolean;
isListening: boolean;
toggleListening: ToggleListening;
toggleListening: () => void;
voiceListeningState: VoiceListeningState;
templates: VoiceSearchTemplates;
};
Expand Down
29 changes: 27 additions & 2 deletions src/connectors/voice-search/__tests__/connectVoiceSearch-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import connectVoiceSearch from '../connectVoiceSearch';

jest.mock('../../../lib/voiceSearchHelper', () => {
return ({ onStateChange, onQueryChange }) => {
let isListening = false;
return {
getState: () => {},
isBrowserSupported: () => true,
isListening: () => false,
toggleListening: () => {},
isListening: () => isListening,
startListening: () => {
isListening = !isListening;
},
dispose: jest.fn(),
// ⬇️ for test
changeState: () => onStateChange(),
Expand Down Expand Up @@ -65,6 +68,28 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/voice-searc
})
);
});

it('creates custom voice helper', () => {
const voiceHelper = {
isBrowserSupported: () => true,
dispose: () => {},
getState: () => ({
isSpeechFinal: true,
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
status: 'askingPermission',
transcript: '',
}),
isListening: () => true,
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
toggleListening: () => {},
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
};

const { widget } = getInitializedWidget({
widgetParams: {
createVoiceSearchHelper: () => voiceHelper,
},
});

expect(widget._voiceSearchHelper).toBe(voiceHelper);
});
});

it('calls renderFn during init and render', () => {
Expand Down
25 changes: 19 additions & 6 deletions src/connectors/voice-search/connectVoiceSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import {
noop,
} from '../../lib/utils';
import { Renderer, RendererOptions, WidgetFactory } from '../../types';
import createVoiceSearchHelper, {
import builtInCreateVoiceSearchHelper from '../../lib/voiceSearchHelper';
import {
CreateVoiceSearchHelper,
VoiceListeningState,
ToggleListening,
} from '../../lib/voiceSearchHelper';
} from '../../lib/voiceSearchHelper/types';

const withUsage = createDocumentationMessageGenerator({
name: 'voice-search',
Expand All @@ -21,12 +22,13 @@ export type VoiceSearchConnectorParams = {
additionalQueryParameters?: (params: {
query: string;
}) => PlainSearchParameters | void;
createVoiceSearchHelper?: CreateVoiceSearchHelper;
};

export type VoiceSearchRendererOptions<TVoiceSearchWidgetParams> = {
isBrowserSupported: boolean;
isListening: boolean;
toggleListening: ToggleListening;
toggleListening: () => void;
voiceListeningState: VoiceListeningState;
} & RendererOptions<TVoiceSearchWidgetParams>;

Expand Down Expand Up @@ -58,15 +60,25 @@ const connectVoiceSearch: VoiceSearchConnector = (
voiceSearchHelper: {
isBrowserSupported,
isListening,
toggleListening,
startListening,
stopListening,
getState,
},
}): void => {
renderFn(
{
isBrowserSupported: isBrowserSupported(),
isListening: isListening(),
toggleListening,
toggleListening() {
if (!isBrowserSupported()) {
return;
}
if (isListening()) {
stopListening();
} else {
startListening();
}
},
voiceListeningState: getState(),
widgetParams,
instantSearchInstance,
Expand All @@ -79,6 +91,7 @@ const connectVoiceSearch: VoiceSearchConnector = (
searchAsYouSpeak = false,
language,
additionalQueryParameters,
createVoiceSearchHelper = builtInCreateVoiceSearchHelper,
} = widgetParams;

return {
Expand Down
12 changes: 6 additions & 6 deletions src/lib/voiceSearchHelper/__tests__/index-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ describe('VoiceSearchHelper', () => {
onStateChange,
});

voiceSearchHelper.toggleListening();
voiceSearchHelper.startListening();
expect(onStateChange).toHaveBeenCalledTimes(1);
expect(voiceSearchHelper.getState().status).toEqual('askingPermission');
simulateListener.start();
Expand Down Expand Up @@ -132,7 +132,7 @@ describe('VoiceSearchHelper', () => {
onStateChange,
});

voiceSearchHelper.toggleListening();
voiceSearchHelper.startListening();
expect(onStateChange).toHaveBeenCalledTimes(1);
expect(voiceSearchHelper.getState().status).toEqual('askingPermission');
simulateListener.start();
Expand Down Expand Up @@ -169,7 +169,7 @@ describe('VoiceSearchHelper', () => {
onQueryChange,
onStateChange,
});
voiceSearchHelper.toggleListening();
voiceSearchHelper.startListening();
expect(voiceSearchHelper.getState().status).toEqual('askingPermission');
simulateListener.error({
error: 'not-allowed',
Expand All @@ -187,7 +187,7 @@ describe('VoiceSearchHelper', () => {
onQueryChange: () => {},
onStateChange: () => {},
});
voiceSearchHelper.toggleListening();
voiceSearchHelper.startListening();
voiceSearchHelper.dispose();
expect(stop).toHaveBeenCalledTimes(1);
});
Expand All @@ -202,8 +202,8 @@ describe('VoiceSearchHelper', () => {
onStateChange,
});

voiceSearchHelper.toggleListening();
voiceSearchHelper.toggleListening();
voiceSearchHelper.startListening();
voiceSearchHelper.stopListening();
expect(voiceSearchHelper.getState().status).toBe('finished');
});
});
54 changes: 8 additions & 46 deletions src/lib/voiceSearchHelper/index.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,11 @@
export type VoiceSearchHelperParams = {
searchAsYouSpeak: boolean;
language?: string;
onQueryChange: (query: string) => void;
onStateChange: () => void;
};

export type Status =
| 'initial'
| 'askingPermission'
| 'waiting'
| 'recognizing'
| 'finished'
| 'error';

export type VoiceListeningState = {
status: Status;
transcript: string;
isSpeechFinal: boolean;
errorCode?: SpeechRecognitionErrorCode;
};
import { CreateVoiceSearchHelper, Status, VoiceListeningState } from './types';

export type VoiceSearchHelper = {
getState: () => VoiceListeningState;
isBrowserSupported: () => boolean;
isListening: () => boolean;
toggleListening: () => void;
dispose: () => void;
};

export type ToggleListening = () => void;

export default function createVoiceSearchHelper({
const createVoiceSearchHelper: CreateVoiceSearchHelper = function createVoiceSearchHelper({
searchAsYouSpeak,
language,
onQueryChange,
onStateChange,
}: VoiceSearchHelperParams): VoiceSearchHelper {
}) {
const SpeechRecognitionAPI: new () => SpeechRecognition =
(window as any).webkitSpeechRecognition ||
(window as any).SpeechRecognition;
Expand Down Expand Up @@ -139,22 +109,14 @@ export default function createVoiceSearchHelper({
resetState('finished');
};

const toggleListening = (): void => {
if (!isBrowserSupported()) {
return;
}
if (isListening()) {
stop();
} else {
start();
}
};

return {
getState,
isBrowserSupported,
isListening,
toggleListening,
startListening: start,
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
stopListening: stop,
dispose,
};
}
};

export default createVoiceSearchHelper;
34 changes: 34 additions & 0 deletions src/lib/voiceSearchHelper/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export type Status =
| 'initial'
| 'askingPermission'
| 'waiting'
| 'recognizing'
| 'finished'
| 'error';

export type VoiceListeningState = {
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
status: Status;
transcript: string;
isSpeechFinal: boolean;
errorCode?: string;
francoischalifour marked this conversation as resolved.
Show resolved Hide resolved
};
Haroenv marked this conversation as resolved.
Show resolved Hide resolved

export type VoiceSearchHelperParams = {
searchAsYouSpeak: boolean;
language?: string;
onQueryChange: (query: string) => void;
onStateChange: () => void;
};

export type VoiceSearchHelper = {
getState: () => VoiceListeningState;
isBrowserSupported: () => boolean;
isListening: () => boolean;
startListening: () => void;
stopListening: () => void;
dispose: () => void;
};

export type CreateVoiceSearchHelper = (
params: VoiceSearchHelperParams
) => VoiceSearchHelper;
30 changes: 28 additions & 2 deletions src/widgets/voice-search/__tests__/voice-search-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
import { createSingleSearchResponse } from '../../../../test/mock/createAPIResponse';
import { castToJestMock } from '../../../../test/utils/castToJestMock';
import { Widget } from '../../../types';
import voiceSearch from '../voice-search';
import voiceSearch, { VoiceSearchWidgetParams } from '../voice-search';
import { VoiceSearchHelper } from '../../../lib/voiceSearchHelper/types';

const render = castToJestMock(preactRender);

Expand All @@ -31,7 +32,9 @@ type DefaultSetupWrapper = {
widgetRender: (helper: Helper) => void;
};

function defaultSetup(opts = {}): DefaultSetupWrapper {
function defaultSetup(
opts: Omit<VoiceSearchWidgetParams, 'container'> = {}
): DefaultSetupWrapper {
const container = document.createElement('div');
const widget = voiceSearch({ container, ...opts });

Expand Down Expand Up @@ -92,6 +95,29 @@ describe('voiceSearch()', () => {
See documentation: https://www.algolia.com/doc/api-reference/widgets/voice-search/js/"
`);
});

it('creates custom voice helper', () => {
const voiceHelper: VoiceSearchHelper = {
isBrowserSupported: () => true,
dispose: () => {},
getState: () => ({
isSpeechFinal: true,
status: 'askingPermission',
transcript: '',
}),
isListening: () => true,
startListening: () => {},
stopListening: () => {},
};

const { widgetInit, widget } = defaultSetup({
createVoiceSearchHelper: () => voiceHelper,
});

widgetInit(helper);

expect((widget as any)._voiceSearchHelper).toBe(voiceHelper);
});
});

describe('Rendering', () => {
Expand Down
6 changes: 5 additions & 1 deletion src/widgets/voice-search/voice-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import VoiceSearch, {
} from '../../components/VoiceSearch/VoiceSearch';
import defaultTemplates from './defaultTemplates';
import { WidgetFactory, Template } from '../../types';
import { CreateVoiceSearchHelper } from '../../lib/voiceSearchHelper/types';

const withUsage = createDocumentationMessageGenerator({ name: 'voice-search' });
const suit = component('VoiceSearch');
Expand All @@ -40,7 +41,7 @@ export type VoiceSearchTemplates = {
status: Template<VoiceSearchTemplateProps>;
};

type VoiceSearchWidgetParams = {
export type VoiceSearchWidgetParams = {
container: string | HTMLElement;
cssClasses?: Partial<VoiceSearchCSSClasses>;
templates?: Partial<VoiceSearchTemplates>;
Expand All @@ -49,6 +50,7 @@ type VoiceSearchWidgetParams = {
additionalQueryParameters?: (params: {
query: string;
}) => PlainSearchParameters | void;
createVoiceSearchHelper?: CreateVoiceSearchHelper;
};

type VoiceSearchRendererWidgetParams = {
Expand Down Expand Up @@ -89,6 +91,7 @@ const voiceSearch: VoiceSearch = (
searchAsYouSpeak = false,
language,
additionalQueryParameters,
createVoiceSearchHelper,
} = {} as VoiceSearchWidgetParams
) => {
if (!container) {
Expand All @@ -114,6 +117,7 @@ const voiceSearch: VoiceSearch = (
searchAsYouSpeak,
language,
additionalQueryParameters,
createVoiceSearchHelper,
});
};

Expand Down