Skip to content

Commit

Permalink
feat(voiceSearch): add connector and widget (#3601)
Browse files Browse the repository at this point in the history
* WIP: add voice search

* feat(voiceSearch): fix css

* feat(voiceSearch): expose errorCode to templaes

* feat(voiceSearch): fix confusing message on story

* feat(voiceSearch): rename isStarted to isListening

* feat(voiceSearch): move WebSpeech-related code to a separate file under lib

* feat(voiceSearch): fix bug

* feat(voiceSearch): move helper-related logic from component to connector

* feat(voiceSearch): rename status

* feat(voiceSearch): change default button text

* feat(voiceSearch): not to pullute global scope

* feat(voiceSearch): disable button if on unsupported browser

* feat(voiceSearch): add option to hideOnUnsupportedBrowser

* feat(voiceSearch): remove unnecessary constructor

* feat(voiceSearch): remove ref and use event.currentTarget

* chore(voiceSearch): use typeof for undefined

* feat(voiceSearch): simplify render function

* feat(voiceSearch): remove transcript template

* feat(voiceSearch): searchAsYouSpeak: false

* fix(voiceSearch): display interimResults correctly even when not in search-as-you-speak

* chore(voiceSearch): remove hideOnUnsupportedBrowser

* chore(featVoice): merge statuses and add new one

* fix(voiceSearch): fix isListening

* feat(voiceSearch): add default status template

* chore(voiceSearch): conditional button icon

* test(voiceSearch): add tests

* chore(voiceSearch): change to TypeScript

* chore(storybook): make searchOptions optional

* chore(voiceSearch): change to TypeScript

* chore(voiceSearch): update style for a test story

* chore(voiceSearch): fix TS warning

* chore(voiceSearch): remove unused comments

* chore(voiceSeach): fix lint errors

* feat(voiceSearch): add isSupportedBrowser to templates

* test(voiceSearch): add test for VoiceSearchHelper

* test(voiceSearch): remove unnecessary exclamation mark

* chore(voiceSearch): function renamed for consistency

* Update src/lib/voiceSearchHelper/index.ts

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* Update src/components/VoiceSearch/VoiceSearch.tsx

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* chore(voiceSearch): use named parameter

* chore(voiceSearch): rename function for consistency

* chore(voiceSearch): pass boolean variable instead of function returning boolean for simplicity

* chore(voiceSearch): remove propTypes + fix list warning

* test(voiceSearch): remove unnecessary story and add desc to the default one

* Update src/lib/voiceSearchHelper/index.ts

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* type(voiceSearch): add type to SpeechRecognition

* Update src/lib/voiceSearchHelper/index.ts

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* Update src/lib/voiceSearchHelper/index.ts

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* Update src/lib/voiceSearchHelper/index.ts

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* Update src/lib/voiceSearchHelper/index.ts

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* type(voiceSearch): make Template generic

* Update src/connectors/voice-search/connectVoiceSearch.ts

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* Update src/lib/voiceSearchHelper/index.ts

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* Update src/lib/voiceSearchHelper/index.ts

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* Update src/connectors/voice-search/connectVoiceSearch.ts

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* chore(voiceSearch): remove rootTagName

* refactor(voiceSearch): make renderer simpler

* chore(voiceSearch): add styles for story

* Update src/components/VoiceSearch/__tests__/VoiceSearch-test.js

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* feat(voiceSearch): add connectVoiceSearch to connectors/index

* test(voiceSearch): remove unused test case

* test(voiceSearch): more strict checking

* test(voiceSearch): more strict checking

* test(voiceSearch): rename

* test(voiceSearch): remove snapshot matching and put actual object to match

* type(voiceSearch): better TS support + tests in TS

* chore(voiceSearch): make searchAsYouSpeak required at component level

* test(voiceSearch): clean up & split a test into many

* test(voiceSearch): add test for unmountFn

* type(voiceSearch): add types to VoiceSearchHelper

* fix(voiceSearch): default value of transcript is undefined, not an empty string

* fix(voiceSearch): provide searchAsYouSpeak directly to voiceSearchHelper

* type(voiceSearch): make cssClasses and templates from VoiceSearchWidgetParams optional

* fix(voiceSearch): null check

* chore(voiceSearch): change order of listeners

* type(voiceSearch): import widget directly from source to get proper definition

* feat(voiceSearch): add getWidgetState, getWidgetSearchParameters for connector

* type(voiceSearch): rename class to avoid type error

* fix(voiceSearch): ignore toggleListening if it's not supported browser

* chore(voiceSearch): rename isSupportedBrowser to isBrowserSupported

* Update src/lib/voiceSearchHelper/index.ts

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* Update src/lib/voiceSearchHelper/index.ts

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* chore(voiceSearch): update css version to 7.3.0

* chore(voiceSearch): remove unused style from story

* type(voiceSearch): fix typing error

* type(voiceSearch): fix lint error

* Update .storybook/preview-head.html

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* type(voiceSearch): fix types in test

* type(voiceSearch): change type in order to store query properly

* type(voiceSearch): fix typing errors

* test(voiceSearch): update test

* chore(voiceSearch): prettify svg

* fix(voiceSearch): add extra description on title on unsupported browser

* chore(voiceSearch): simplify svg

* test(voiceSearch): modify story with custom style

* chore(voiceSearch): replace if-else with ternary operator

* chore(voiceSearch): replace ternary operator with if

* chore(voiceSearch): increase bundlesize limit

* Revert "chore(voiceSearch): increase bundlesize limit"

This reverts commit 63fcde1.

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

Co-Authored-By: eunjae-lee <karis612@gmail.com>

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

Co-Authored-By: eunjae-lee <karis612@gmail.com>

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

Co-Authored-By: eunjae-lee <karis612@gmail.com>

* test(voiceSearch): improve tests

* test(voiceSearch): improve tests

* chore(voiceSearch): clean up imports

* type(voiceSearch): fix lint errors
  • Loading branch information
eunjae-lee authored Apr 29, 2019
1 parent ab908a0 commit 21e4d81
Show file tree
Hide file tree
Showing 16 changed files with 1,401 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .storybook/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const withHits = (
instantsearch: any;
search: any;
}) => void,
searchOptions: any
searchOptions?: any
) => () => {
const {
appId = 'latency',
Expand Down
80 changes: 80 additions & 0 deletions src/components/VoiceSearch/VoiceSearch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'preact-compat';
import Template from '../Template/Template';

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

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

export type VoiceSearchProps = {
cssClasses: VoiceSearchCSSClasses;
isBrowserSupported: boolean;
isListening: boolean;
toggleListening: ToggleListening;
voiceListeningState: VoiceListeningState;
templates: VoiceSearchTemplates;
};

const VoiceSearch = ({
cssClasses,
isBrowserSupported,
isListening,
toggleListening,
voiceListeningState,
templates,
}: VoiceSearchProps): React.ReactNode => {
const handleClick = (event: React.MouseEvent<HTMLElement>): void => {
event.currentTarget.blur();
toggleListening();
};

const { status, transcript, isSpeechFinal, errorCode } = voiceListeningState;
return (
<div className={cssClasses.root}>
<Template
templateKey="buttonText"
rootTagName="button"
rootProps={{
className: cssClasses.button,
type: 'button',
title: `Search by voice${
isBrowserSupported ? '' : ' (not supported on this browser)'
}`,
onClick: handleClick,
disabled: !isBrowserSupported,
}}
data={{
status,
errorCode,
isListening,
transcript,
isSpeechFinal,
isBrowserSupported,
}}
templates={templates}
/>
<Template
templateKey="status"
rootProps={{
className: cssClasses.status,
}}
data={{
status,
errorCode,
isListening,
transcript,
isSpeechFinal,
isBrowserSupported,
}}
templates={templates}
/>
</div>
);
};

export default VoiceSearch;
102 changes: 102 additions & 0 deletions src/components/VoiceSearch/__tests__/VoiceSearch-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React from 'react';
import { mount } from 'enzyme';
import VoiceSearch, { VoiceSearchProps } from '../VoiceSearch';

const defaultProps: VoiceSearchProps = {
cssClasses: {
root: 'root',
button: 'button',
status: 'status',
},
isBrowserSupported: true,
isListening: false,
toggleListening: () => {},
voiceListeningState: {
status: 'initial',
},
templates: {
buttonText: 'button',
status: 'status',
},
};

describe('VoiceSearch', () => {
describe('button', () => {
it('call toggleListening when button is clicked', () => {
const props = {
...defaultProps,
toggleListening: jest.fn(),
};
const wrapper = mount(<VoiceSearch {...props} />);
wrapper.find('button').simulate('click');
expect(props.toggleListening).toHaveBeenCalledTimes(1);
});
});

describe('Rendering', () => {
it('with default props', () => {
expect(mount(<VoiceSearch {...defaultProps} />)).toMatchSnapshot();
});

it('button disabled in unsupported browser', () => {
const props = {
...defaultProps,
isBrowserSupported: false,
};
const wrapper = mount(<VoiceSearch {...props} />);
expect(wrapper.find('button').props().disabled).toBe(true);
});

it('with custom template for buttonText (1)', () => {
const props = {
...defaultProps,
isListening: true,
templates: {
buttonText: ({ isListening }) => (isListening ? 'Stop' : 'Start'),
status: ``,
},
};
const wrapper = mount(<VoiceSearch {...props} />);
expect(wrapper.find('button').text()).toBe('Stop');
});

it('with custom template for buttonText (2)', () => {
const props = {
...defaultProps,
isListening: false,
templates: {
buttonText: ({ isListening }) => (isListening ? 'Stop' : 'Start'),
status: ``,
},
};
const wrapper = mount(<VoiceSearch {...props} />);
expect(wrapper.find('button').text()).toBe('Start');
});

it('with custom template for status', () => {
const props = {
...defaultProps,
isListening: true,
voiceListeningState: {
status: 'recognizing',
transcript: 'Hello',
isSpeechFinal: false,
errorCode: undefined,
},
templates: {
buttonText: ``,
status: `
<p>status: {{status}}</p>
<p>errorCode: {{errorCode}}</p>
<p>isListening: {{isListening}}</p>
<p>transcript: {{transcript}}</p>
<p>isSpeechFinal: {{isSpeechFinal}}</p>
<p>isBrowserSupported: {{isBrowserSupported}}</p>
`,
},
};
const wrapper = mount(<VoiceSearch {...props} />);
expect(wrapper.find('.status')).toMatchSnapshot();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VoiceSearch Rendering with custom template for status 1`] = `
<div
className="status"
dangerouslySetInnerHTML={
Object {
"__html": "<p>status: recognizing</p> <p>errorCode: </p> <p>isListening: true</p> <p>transcript: Hello</p> <p>isSpeechFinal: false</p> <p>isBrowserSupported: true</p>",
}
}
/>
`;

exports[`VoiceSearch Rendering with default props 1`] = `
<div
className="root"
>
<button
className="button"
dangerouslySetInnerHTML={
Object {
"__html": "button",
}
}
disabled={false}
onClick={[Function]}
title="Search by voice"
type="button"
/>
<div
className="status"
dangerouslySetInnerHTML={
Object {
"__html": "status",
}
}
/>
</div>
`;
3 changes: 3 additions & 0 deletions src/connectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ export {
default as connectAutocomplete,
} from './autocomplete/connectAutocomplete';
export { default as connectQueryRules } from './query-rules/connectQueryRules';
export {
default as connectVoiceSearch,
} from './voice-search/connectVoiceSearch';
Loading

0 comments on commit 21e4d81

Please sign in to comment.