This repository has been archived by the owner on Dec 30, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(voiceSearch): add voice search component (#668)
* chore(voiceSearch): scope styles to non-ais elements * feat(voiceSearch): WIP adding voiceSearch component * test(voiceSearch): add stories * test(voiceSearch): add tests for the component * chore(voiceSearch): formatted * chore(voiceSearch): fix lint errors * doc: revert a mistake * chore(voiceSearch): remove unnecessary computed properties * feat(voiceSearch): provide all data to the default root slot * feat(voiceSearch): change slot properties to kebab case * feat(voiceSearch): add a computed property for root slot props * chore(voiceSearch): replace temp files with the ones from latest IS.js * fix(voiceSearch): fix an import path * chore(voiceSearch): update bundlesize * Apply suggestions from code review Co-Authored-By: Haroen Viaene <fingebimus@me.com> * chore: update IS.js@3.5.1 * chore: update bundlesize
- Loading branch information
Eunjae Lee
authored
May 21, 2019
1 parent
2a21b52
commit 84efffc
Showing
8 changed files
with
486 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
<template> | ||
<div | ||
v-if="state" | ||
:class="suit()" | ||
> | ||
<slot v-bind="rootSlotProps"> | ||
<button | ||
type="button" | ||
:class="suit('button')" | ||
:title="state.isBrowserSupported ? buttonTitle : disabledButtonTitle" | ||
:disabled="!state.isBrowserSupported" | ||
@click="handleClick" | ||
> | ||
<slot | ||
name="buttonText" | ||
v-bind="innerSlotProps" | ||
> | ||
<svg | ||
v-bind="buttonSvgAttrs" | ||
v-if="errorNotAllowed" | ||
> | ||
<line | ||
x1="1" | ||
y1="1" | ||
x2="23" | ||
y2="23" | ||
/> | ||
<path d="M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6" /> | ||
<path d="M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23" /> | ||
<line | ||
x1="12" | ||
y1="19" | ||
x2="12" | ||
y2="23" | ||
/> | ||
<line | ||
x1="8" | ||
y1="23" | ||
x2="16" | ||
y2="23" | ||
/> | ||
</svg> | ||
<svg | ||
v-bind="buttonSvgAttrs" | ||
v-else | ||
> | ||
<path | ||
d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" | ||
:fill="state.isListening ? 'currentColor' : 'none'" | ||
/> | ||
<path d="M19 10v2a7 7 0 0 1-14 0v-2" /> | ||
<line | ||
x1="12" | ||
y1="19" | ||
x2="12" | ||
y2="23" | ||
/> | ||
<line | ||
x1="8" | ||
y1="23" | ||
x2="16" | ||
y2="23" | ||
/> | ||
</svg> | ||
</slot> | ||
</button> | ||
<div :class="suit('status')"> | ||
<slot | ||
name="status" | ||
v-bind="innerSlotProps" | ||
> | ||
<p>{{ state.voiceListeningState.transcript }}</p> | ||
</slot> | ||
</div> | ||
</slot> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import { connectVoiceSearch } from 'instantsearch.js/es/connectors'; | ||
import { createSuitMixin } from '../mixins/suit'; | ||
import { createWidgetMixin } from '../mixins/widget'; | ||
export default { | ||
name: 'AisVoiceSearch', | ||
mixins: [ | ||
createWidgetMixin({ connector: connectVoiceSearch }), | ||
createSuitMixin({ name: 'VoiceSearch' }), | ||
], | ||
props: { | ||
searchAsYouSpeak: { | ||
type: Boolean, | ||
required: false, | ||
default: false, | ||
}, | ||
buttonTitle: { | ||
type: String, | ||
required: false, | ||
default: 'Search by voice', | ||
}, | ||
disabledButtonTitle: { | ||
type: String, | ||
required: false, | ||
default: 'Search by voice (not supported on this browser)', | ||
}, | ||
}, | ||
data() { | ||
return { | ||
buttonSvgAttrs: { | ||
xmlns: 'http://www.w3.org/2000/svg', | ||
width: '16', | ||
height: '16', | ||
viewBox: '0 0 24 24', | ||
fill: 'none', | ||
stroke: 'currentColor', | ||
strokeWidth: '2', | ||
strokeLinecap: 'round', | ||
strokeLinejoin: 'round', | ||
}, | ||
}; | ||
}, | ||
computed: { | ||
widgetParams() { | ||
return { | ||
searchAsYouSpeak: this.searchAsYouSpeak, | ||
}; | ||
}, | ||
errorNotAllowed() { | ||
return ( | ||
this.state.voiceListeningState.status === 'error' && | ||
this.state.voiceListeningState.errorCode === 'not-allowed' | ||
); | ||
}, | ||
rootSlotProps() { | ||
return { | ||
isBrowserSupported: this.state.isBrowserSupported, | ||
isListening: this.state.isListening, | ||
toggleListening: this.state.toggleListening, | ||
voiceListeningState: this.state.voiceListeningState, | ||
}; | ||
}, | ||
innerSlotProps() { | ||
return { | ||
status: this.state.voiceListeningState.status, | ||
errorCode: this.state.voiceListeningState.errorCode, | ||
isListening: this.state.isListening, | ||
transcript: this.state.voiceListeningState.transcript, | ||
isSpeechFinal: this.state.voiceListeningState.isSpeechFinal, | ||
isBrowserSupported: this.state.isBrowserSupported, | ||
}; | ||
}, | ||
}, | ||
methods: { | ||
handleClick(event) { | ||
event.currentTarget.blur(); | ||
this.state.toggleListening(); | ||
}, | ||
}, | ||
}; | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import VoiceSearch from '../VoiceSearch.vue'; | ||
import { mount } from '@vue/test-utils'; | ||
import { __setState } from '../../mixins/widget'; | ||
jest.mock('../../mixins/widget'); | ||
|
||
const defaultState = { | ||
voiceListeningState: { | ||
status: 'initial', | ||
transcript: undefined, | ||
isSpeechFinal: undefined, | ||
errorCode: undefined, | ||
}, | ||
isBrowserSupported: true, | ||
isListening: false, | ||
toggleListening: jest.fn(), | ||
}; | ||
|
||
const buttonTextScopedSlot = ` | ||
<span slot-scope="{ isListening }"> | ||
{{isListening ? "Stop": "Start"}} | ||
</span> | ||
`; | ||
|
||
describe('button', () => { | ||
it('calls toggleListening when the button is clicked', () => { | ||
__setState(defaultState); | ||
const wrapper = mount(VoiceSearch); | ||
wrapper.find('button').trigger('click'); | ||
expect(wrapper.vm.state.toggleListening).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
|
||
describe('Rendering', () => { | ||
it('renders default template correctly', () => { | ||
__setState(defaultState); | ||
const wrapper = mount(VoiceSearch); | ||
expect(wrapper.html()).toMatchSnapshot(); | ||
}); | ||
|
||
it('display the button as disabled on unsupported browser', () => { | ||
__setState({ | ||
...defaultState, | ||
isBrowserSupported: false, | ||
}); | ||
const wrapper = mount(VoiceSearch); | ||
expect(wrapper.find('button').attributes().disabled).toBe('disabled'); | ||
}); | ||
|
||
it('with custom template for buttonText (1)', () => { | ||
__setState({ | ||
...defaultState, | ||
isListening: true, | ||
}); | ||
const wrapper = mount(VoiceSearch, { | ||
scopedSlots: { | ||
buttonText: buttonTextScopedSlot, | ||
}, | ||
}); | ||
expect(wrapper.find('button').text()).toBe('Stop'); | ||
}); | ||
|
||
it('with custom template for buttonText (2)', () => { | ||
__setState({ | ||
...defaultState, | ||
isListening: false, | ||
}); | ||
const wrapper = mount(VoiceSearch, { | ||
scopedSlots: { | ||
buttonText: buttonTextScopedSlot, | ||
}, | ||
}); | ||
expect(wrapper.find('button').text()).toBe('Start'); | ||
}); | ||
|
||
it('with custom template for status', () => { | ||
__setState({ | ||
voiceListeningState: { | ||
status: 'recognizing', | ||
transcript: 'Hello', | ||
isSpeechFinal: false, | ||
errorCode: undefined, | ||
}, | ||
isBrowserSupported: true, | ||
isListening: true, | ||
toggleListening: jest.fn(), | ||
}); | ||
const wrapper = mount(VoiceSearch, { | ||
scopedSlots: { | ||
status: ` | ||
<div slot-scope="{ status, errorCode, isListening, transcript, isSpeechFinal, isBrowserSupported }"> | ||
<p>status: {{status}}</p> | ||
<p>errorCode: {{errorCode}}</p> | ||
<p>isListening: {{isListening}}</p> | ||
<p>transcript: {{transcript}}</p> | ||
<p>isSpeechFinal: {{isSpeechFinal}}</p> | ||
<p>isBrowserSupported: {{isBrowserSupported}}</p> | ||
</div> | ||
`, | ||
}, | ||
}); | ||
expect(wrapper.find('.ais-VoiceSearch-status').html()).toMatchSnapshot(); | ||
}); | ||
}); |
73 changes: 73 additions & 0 deletions
73
src/components/__tests__/__snapshots__/VoiceSearch.js.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Rendering renders default template correctly 1`] = ` | ||
<div class="ais-VoiceSearch"> | ||
<button type="button" | ||
title="Search by voice" | ||
class="ais-VoiceSearch-button" | ||
> | ||
<svg xmlns="http://www.w3.org/2000/svg" | ||
width="16" | ||
height="16" | ||
viewbox="0 0 24 24" | ||
fill="none" | ||
stroke="currentColor" | ||
strokewidth="2" | ||
strokelinecap="round" | ||
strokelinejoin="round" | ||
> | ||
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" | ||
fill="none" | ||
> | ||
</path> | ||
<path d="M19 10v2a7 7 0 0 1-14 0v-2"> | ||
</path> | ||
<line x1="12" | ||
y1="19" | ||
x2="12" | ||
y2="23" | ||
> | ||
</line> | ||
<line x1="8" | ||
y1="23" | ||
x2="16" | ||
y2="23" | ||
> | ||
</line> | ||
</svg> | ||
</button> | ||
<div class="ais-VoiceSearch-status"> | ||
<p> | ||
</p> | ||
</div> | ||
</div> | ||
`; | ||
|
||
exports[`Rendering with custom template for status 1`] = ` | ||
<div class="ais-VoiceSearch-status"> | ||
<div> | ||
<p> | ||
status: recognizing | ||
</p> | ||
<p> | ||
errorCode: | ||
</p> | ||
<p> | ||
isListening: true | ||
</p> | ||
<p> | ||
transcript: Hello | ||
</p> | ||
<p> | ||
isSpeechFinal: false | ||
</p> | ||
<p> | ||
isBrowserSupported: true | ||
</p> | ||
</div> | ||
</div> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.