@untemps/react-vocal 1.7.37 → 2.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -2
- package/README.md +72 -31
- package/dist/index.es.js +567 -2
- package/dist/index.js +7 -2
- package/package.json +39 -65
- package/.github/workflows/publish.yml +0 -30
- package/.prettierignore +0 -2
- package/.prettierrc +0 -29
- package/CLAUDE.md +0 -55
- package/assets/icon-idle.png +0 -0
- package/assets/icon-listening.png +0 -0
- package/assets/microphone.png +0 -0
- package/assets/react-vocal.png +0 -0
- package/babel.config.js +0 -12
- package/commitlint.config.js +0 -7
- package/dev/babel.config.js +0 -4
- package/dev/package.json +0 -14
- package/dev/public/index.html +0 -24
- package/dev/rollup.config.js +0 -29
- package/dev/src/index.js +0 -46
- package/dev/yarn.lock +0 -201
- package/dist/index.umd.js +0 -2
- package/jest/jest.setup.js +0 -72
- package/rollup.config.js +0 -42
- package/src/components/Icon.js +0 -37
- package/src/components/Vocal.js +0 -235
- package/src/components/__tests__/Icon.test.js +0 -42
- package/src/components/__tests__/Vocal.test.js +0 -273
- package/src/components/__tests__/VocalWithMockedUseVocal.test.js +0 -40
- package/src/hooks/__tests__/useCommands.test.js +0 -64
- package/src/hooks/__tests__/useTimeout.test.js +0 -69
- package/src/hooks/__tests__/useVocal.test.js +0 -197
- package/src/hooks/useCommands.js +0 -21
- package/src/hooks/useTimeout.js +0 -23
- package/src/hooks/useVocal.js +0 -58
- package/src/index.js +0 -7
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jest-environment jsdom
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import React from 'react'
|
|
6
|
-
import { waitFor } from '@testing-library/dom'
|
|
7
|
-
import { act, fireEvent, render } from '@testing-library/react'
|
|
8
|
-
|
|
9
|
-
import Vocal from '../Vocal'
|
|
10
|
-
|
|
11
|
-
jest.mock('../../hooks/useVocal', () => {
|
|
12
|
-
return () => [
|
|
13
|
-
null,
|
|
14
|
-
{
|
|
15
|
-
subscribe: () => {
|
|
16
|
-
throw new Error('Foo')
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
]
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
const defaultProps = {}
|
|
23
|
-
const getInstance = (props = {}, children = null) => (
|
|
24
|
-
<Vocal {...defaultProps} {...props}>
|
|
25
|
-
{children}
|
|
26
|
-
</Vocal>
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
describe('Vocal', () => {
|
|
30
|
-
it('triggers onError handler', async () => {
|
|
31
|
-
const onError = jest.fn()
|
|
32
|
-
const { queryByTestId } = render(getInstance({ onError }))
|
|
33
|
-
await act(async () => {
|
|
34
|
-
fireEvent.click(queryByTestId('__vocal-root__'))
|
|
35
|
-
await waitFor(() => expect(onError).toHaveBeenCalled())
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
// TODO: Merge this file with Vocal.test.js
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { renderHook } from '@testing-library/react-hooks'
|
|
2
|
-
|
|
3
|
-
import useCommands from '../useCommands'
|
|
4
|
-
|
|
5
|
-
describe('useCommands', () => {
|
|
6
|
-
it('returns triggerCommand function', () => {
|
|
7
|
-
const triggerCommand = renderHook(() => useCommands())
|
|
8
|
-
expect(triggerCommand).toBeDefined()
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it('triggers callback mapped to the exact input', () => {
|
|
12
|
-
const commands = {
|
|
13
|
-
foo: () => 'bar',
|
|
14
|
-
}
|
|
15
|
-
const {
|
|
16
|
-
result: { current: triggerCommand },
|
|
17
|
-
} = renderHook(() => useCommands(commands))
|
|
18
|
-
expect(triggerCommand('foo')).toBe('bar')
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('passes input as callback argument', () => {
|
|
22
|
-
const commands = {
|
|
23
|
-
foo: (input) => input,
|
|
24
|
-
}
|
|
25
|
-
const {
|
|
26
|
-
result: { current: triggerCommand },
|
|
27
|
-
} = renderHook(() => useCommands(commands))
|
|
28
|
-
expect(triggerCommand('foo')).toBe('foo')
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
describe('Approximate inputs', () => {
|
|
32
|
-
const value = 'foo'
|
|
33
|
-
it.each([
|
|
34
|
-
['Change la bordure en vert', 'Change la bordure en verre', value],
|
|
35
|
-
['Change la bordure en vert', 'Change la bordure en verres', value],
|
|
36
|
-
['Change la bordure en vert', 'Change la bordure en vers', value],
|
|
37
|
-
['Change la bordure en vert', 'Change la bordure en vairs', value],
|
|
38
|
-
['Change la bordure en vert', 'Changez la bordure en verre', value],
|
|
39
|
-
['Change la bordure en vert', 'Changez la bodure en verre', null],
|
|
40
|
-
['Change la bordure en vert', 'Change la bordure en rouge', null],
|
|
41
|
-
['Change la bordure en vert', 'Change la bordure en verre de rouge', null],
|
|
42
|
-
['Change la bordure en vert', 'Change la bordure en violet', null],
|
|
43
|
-
['Change la bordure en vert', 'Modifie la bordure en violet', null],
|
|
44
|
-
])('triggers callback mapped to approximate inputs', (command, input, expected) => {
|
|
45
|
-
const commands = {
|
|
46
|
-
[command]: () => value,
|
|
47
|
-
}
|
|
48
|
-
const {
|
|
49
|
-
result: { current: triggerCommand },
|
|
50
|
-
} = renderHook(() => useCommands(commands))
|
|
51
|
-
expect(triggerCommand(input)).toBe(expected)
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('returns null as no command is mapped to the input', () => {
|
|
56
|
-
const commands = {
|
|
57
|
-
foo: () => 'bar',
|
|
58
|
-
}
|
|
59
|
-
const {
|
|
60
|
-
result: { current: triggerCommand },
|
|
61
|
-
} = renderHook(() => useCommands(commands))
|
|
62
|
-
expect(triggerCommand('gag')).toBeNull()
|
|
63
|
-
})
|
|
64
|
-
})
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { renderHook } from '@testing-library/react-hooks'
|
|
2
|
-
|
|
3
|
-
import useTimeout from '../useTimeout'
|
|
4
|
-
|
|
5
|
-
const wait = (delay) => {
|
|
6
|
-
return new Promise((resolve) => {
|
|
7
|
-
setTimeout(resolve, delay)
|
|
8
|
-
})
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe('useTimeout', () => {
|
|
12
|
-
it('not triggers handler before calling start', () => {
|
|
13
|
-
const handler = jest.fn()
|
|
14
|
-
renderHook(() => useTimeout(handler))
|
|
15
|
-
expect(handler).not.toHaveBeenCalled()
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('not triggers handler before timeout', async () => {
|
|
19
|
-
const handler = jest.fn()
|
|
20
|
-
const timeout = 500
|
|
21
|
-
const {
|
|
22
|
-
result: {
|
|
23
|
-
current: [start],
|
|
24
|
-
},
|
|
25
|
-
} = renderHook(() => useTimeout(handler, timeout))
|
|
26
|
-
start()
|
|
27
|
-
await wait(timeout - 50)
|
|
28
|
-
expect(handler).not.toHaveBeenCalled()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('triggers handler immediately', async () => {
|
|
32
|
-
const handler = jest.fn()
|
|
33
|
-
const {
|
|
34
|
-
result: {
|
|
35
|
-
current: [start],
|
|
36
|
-
},
|
|
37
|
-
} = renderHook(() => useTimeout(handler))
|
|
38
|
-
start()
|
|
39
|
-
await wait(0)
|
|
40
|
-
expect(handler).toHaveBeenCalled()
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('triggers handler after delay', async () => {
|
|
44
|
-
const handler = jest.fn()
|
|
45
|
-
const timeout = 500
|
|
46
|
-
const {
|
|
47
|
-
result: {
|
|
48
|
-
current: [start],
|
|
49
|
-
},
|
|
50
|
-
} = renderHook(() => useTimeout(handler, timeout))
|
|
51
|
-
start()
|
|
52
|
-
await wait(timeout)
|
|
53
|
-
expect(handler).toHaveBeenCalled()
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('not triggers handler if stop is called before timeout', async () => {
|
|
57
|
-
const handler = jest.fn()
|
|
58
|
-
const timeout = 500
|
|
59
|
-
const {
|
|
60
|
-
result: {
|
|
61
|
-
current: [start, stop],
|
|
62
|
-
},
|
|
63
|
-
} = renderHook(() => useTimeout(handler, timeout))
|
|
64
|
-
start()
|
|
65
|
-
stop()
|
|
66
|
-
await wait(timeout)
|
|
67
|
-
expect(handler).not.toHaveBeenCalled()
|
|
68
|
-
})
|
|
69
|
-
})
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { renderHook } from '@testing-library/react-hooks'
|
|
2
|
-
import { Vocal as SpeechRecognitionWrapper } from '@untemps/vocal'
|
|
3
|
-
|
|
4
|
-
import useVocal from '../useVocal'
|
|
5
|
-
|
|
6
|
-
jest.mock('@untemps/vocal')
|
|
7
|
-
|
|
8
|
-
describe('useVocal', () => {
|
|
9
|
-
const mockStart = jest.fn()
|
|
10
|
-
const mockStop = jest.fn()
|
|
11
|
-
const mockAbort = jest.fn()
|
|
12
|
-
const mockAddEventListener = jest.fn()
|
|
13
|
-
const mockRemoveEventListener = jest.fn()
|
|
14
|
-
const mockCleanup = jest.fn()
|
|
15
|
-
|
|
16
|
-
const mockIsSupported = jest.fn()
|
|
17
|
-
Object.defineProperty(SpeechRecognitionWrapper, 'isSupported', {
|
|
18
|
-
get: mockIsSupported,
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
describe('with no SpeechRecognition support', () => {
|
|
22
|
-
beforeAll(() => {
|
|
23
|
-
mockIsSupported.mockReturnValue(false)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('cannot create SpeechRecognition instance', () => {
|
|
27
|
-
const {
|
|
28
|
-
result: {
|
|
29
|
-
current: [ref],
|
|
30
|
-
},
|
|
31
|
-
} = renderHook(() => useVocal())
|
|
32
|
-
expect(ref.current).toBeNull()
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('not triggers start function', () => {
|
|
36
|
-
const {
|
|
37
|
-
result: {
|
|
38
|
-
current: [, { start }],
|
|
39
|
-
},
|
|
40
|
-
} = renderHook(() => useVocal())
|
|
41
|
-
start()
|
|
42
|
-
expect(mockStart).not.toHaveBeenCalled()
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('not triggers stop function', () => {
|
|
46
|
-
const {
|
|
47
|
-
result: {
|
|
48
|
-
current: [, { stop }],
|
|
49
|
-
},
|
|
50
|
-
} = renderHook(() => useVocal())
|
|
51
|
-
stop()
|
|
52
|
-
expect(mockStop).not.toHaveBeenCalled()
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('not triggers abort function', () => {
|
|
56
|
-
const {
|
|
57
|
-
result: {
|
|
58
|
-
current: [, { abort }],
|
|
59
|
-
},
|
|
60
|
-
} = renderHook(() => useVocal())
|
|
61
|
-
abort()
|
|
62
|
-
expect(mockAbort).not.toHaveBeenCalled()
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('not triggers clean function', () => {
|
|
66
|
-
const {
|
|
67
|
-
result: {
|
|
68
|
-
current: [, { clean }],
|
|
69
|
-
},
|
|
70
|
-
} = renderHook(() => useVocal())
|
|
71
|
-
clean()
|
|
72
|
-
expect(mockCleanup).not.toHaveBeenCalled()
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('not triggers subscribe function', () => {
|
|
76
|
-
const {
|
|
77
|
-
result: {
|
|
78
|
-
current: [, { subscribe }],
|
|
79
|
-
},
|
|
80
|
-
} = renderHook(() => useVocal())
|
|
81
|
-
subscribe('foo', jest.fn())
|
|
82
|
-
expect(mockAddEventListener).not.toHaveBeenCalled()
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('not triggers unsubscribe function', () => {
|
|
86
|
-
const {
|
|
87
|
-
result: {
|
|
88
|
-
current: [, { unsubscribe }],
|
|
89
|
-
},
|
|
90
|
-
} = renderHook(() => useVocal())
|
|
91
|
-
unsubscribe('foo', jest.fn())
|
|
92
|
-
expect(mockRemoveEventListener).not.toHaveBeenCalled()
|
|
93
|
-
})
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
describe('with SpeechRecognition support', () => {
|
|
97
|
-
beforeAll(() => {
|
|
98
|
-
mockIsSupported.mockReturnValue(true)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
beforeEach(() => {
|
|
102
|
-
SpeechRecognitionWrapper.mockImplementation(() => {
|
|
103
|
-
return {
|
|
104
|
-
start: mockStart,
|
|
105
|
-
stop: mockStop,
|
|
106
|
-
abort: mockAbort,
|
|
107
|
-
addEventListener: mockAddEventListener,
|
|
108
|
-
removeEventListener: mockRemoveEventListener,
|
|
109
|
-
cleanup: mockCleanup,
|
|
110
|
-
}
|
|
111
|
-
})
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
afterEach(() => {
|
|
115
|
-
SpeechRecognitionWrapper.mockReset()
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('creates SpeechRecognition instance', () => {
|
|
119
|
-
const {
|
|
120
|
-
result: {
|
|
121
|
-
current: [ref],
|
|
122
|
-
},
|
|
123
|
-
} = renderHook(() => useVocal())
|
|
124
|
-
expect(ref.current).toBeDefined()
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
it('uses custom SpeechRecognition instance', () => {
|
|
128
|
-
const foo = new SpeechRecognitionWrapper()
|
|
129
|
-
const {
|
|
130
|
-
result: {
|
|
131
|
-
current: [ref],
|
|
132
|
-
},
|
|
133
|
-
} = renderHook(() => useVocal(null, null, foo))
|
|
134
|
-
expect(ref.current).toBe(foo)
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('triggers start function', () => {
|
|
138
|
-
const {
|
|
139
|
-
result: {
|
|
140
|
-
current: [, { start }],
|
|
141
|
-
},
|
|
142
|
-
} = renderHook(() => useVocal())
|
|
143
|
-
start()
|
|
144
|
-
expect(mockStart).toHaveBeenCalled()
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('triggers stop function', () => {
|
|
148
|
-
const {
|
|
149
|
-
result: {
|
|
150
|
-
current: [, { stop }],
|
|
151
|
-
},
|
|
152
|
-
} = renderHook(() => useVocal())
|
|
153
|
-
stop()
|
|
154
|
-
expect(mockStop).toHaveBeenCalled()
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('triggers abort function', () => {
|
|
158
|
-
const {
|
|
159
|
-
result: {
|
|
160
|
-
current: [, { abort }],
|
|
161
|
-
},
|
|
162
|
-
} = renderHook(() => useVocal())
|
|
163
|
-
abort()
|
|
164
|
-
expect(mockAbort).toHaveBeenCalled()
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
it('triggers clean function', () => {
|
|
168
|
-
const {
|
|
169
|
-
result: {
|
|
170
|
-
current: [, { clean }],
|
|
171
|
-
},
|
|
172
|
-
} = renderHook(() => useVocal())
|
|
173
|
-
clean()
|
|
174
|
-
expect(mockCleanup).toHaveBeenCalled()
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
it('triggers subscribe function', () => {
|
|
178
|
-
const {
|
|
179
|
-
result: {
|
|
180
|
-
current: [, { subscribe }],
|
|
181
|
-
},
|
|
182
|
-
} = renderHook(() => useVocal())
|
|
183
|
-
subscribe('foo', jest.fn())
|
|
184
|
-
expect(mockAddEventListener).toHaveBeenCalled()
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it('triggers unsubscribe function', () => {
|
|
188
|
-
const {
|
|
189
|
-
result: {
|
|
190
|
-
current: [, { unsubscribe }],
|
|
191
|
-
},
|
|
192
|
-
} = renderHook(() => useVocal())
|
|
193
|
-
unsubscribe('foo', jest.fn())
|
|
194
|
-
expect(mockRemoveEventListener).toHaveBeenCalled()
|
|
195
|
-
})
|
|
196
|
-
})
|
|
197
|
-
})
|
package/src/hooks/useCommands.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import Fuse from 'fuse.js'
|
|
2
|
-
|
|
3
|
-
const useCommands = (commands, precision = 0.4) => {
|
|
4
|
-
commands = !!commands
|
|
5
|
-
? Object.entries(commands)?.reduce((acc, [key, value]) => ({ [key.toLowerCase()]: value }), {})
|
|
6
|
-
: {}
|
|
7
|
-
|
|
8
|
-
const triggerCommand = (input) => {
|
|
9
|
-
const fuse = new Fuse(Object.keys(commands), { includeScore: true, ignoreLocation: true })
|
|
10
|
-
const result = fuse.search(input).filter((r) => r.score < precision)
|
|
11
|
-
if (!!result?.length) {
|
|
12
|
-
const key = result[0].item.toLowerCase()
|
|
13
|
-
return commands[key]?.(input)
|
|
14
|
-
}
|
|
15
|
-
return null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return triggerCommand
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default useCommands
|
package/src/hooks/useTimeout.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef } from 'react'
|
|
2
|
-
|
|
3
|
-
const useTimeout = (handler, timeout = 0) => {
|
|
4
|
-
const ref = useRef(-1)
|
|
5
|
-
|
|
6
|
-
const stop = useCallback(() => {
|
|
7
|
-
clearTimeout(ref.current)
|
|
8
|
-
ref.current = -1
|
|
9
|
-
}, [])
|
|
10
|
-
|
|
11
|
-
const start = useCallback(() => {
|
|
12
|
-
stop()
|
|
13
|
-
ref.current = setTimeout(handler, timeout)
|
|
14
|
-
}, [handler, timeout, stop])
|
|
15
|
-
|
|
16
|
-
useEffect(() => stop, [stop])
|
|
17
|
-
|
|
18
|
-
return [start, stop]
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default useTimeout
|
|
22
|
-
|
|
23
|
-
// TODO: Return a promise
|
package/src/hooks/useVocal.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef } from 'react'
|
|
2
|
-
import { Vocal as SpeechRecognitionWrapper } from '@untemps/vocal'
|
|
3
|
-
|
|
4
|
-
const useVocal = (lang = 'en-US', grammars = null, __rsInstance = null) => {
|
|
5
|
-
const ref = useRef(null)
|
|
6
|
-
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
if (SpeechRecognitionWrapper.isSupported) {
|
|
9
|
-
ref.current = __rsInstance || new SpeechRecognitionWrapper({ lang, grammars })
|
|
10
|
-
return () => {
|
|
11
|
-
ref.current.abort()
|
|
12
|
-
ref.current.cleanup()
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}, [lang, grammars, __rsInstance])
|
|
16
|
-
|
|
17
|
-
const start = useCallback(() => {
|
|
18
|
-
if (ref.current) {
|
|
19
|
-
ref.current.start()
|
|
20
|
-
}
|
|
21
|
-
}, [])
|
|
22
|
-
|
|
23
|
-
const stop = useCallback(() => {
|
|
24
|
-
if (ref.current) {
|
|
25
|
-
ref.current.stop()
|
|
26
|
-
}
|
|
27
|
-
}, [])
|
|
28
|
-
|
|
29
|
-
const abort = useCallback(() => {
|
|
30
|
-
if (ref.current) {
|
|
31
|
-
ref.current.abort()
|
|
32
|
-
}
|
|
33
|
-
}, [])
|
|
34
|
-
|
|
35
|
-
const subscribe = useCallback((eventType, handler) => {
|
|
36
|
-
if (ref.current) {
|
|
37
|
-
ref.current.addEventListener(eventType, handler)
|
|
38
|
-
}
|
|
39
|
-
}, [])
|
|
40
|
-
|
|
41
|
-
const unsubscribe = useCallback((eventType, handler) => {
|
|
42
|
-
if (ref.current) {
|
|
43
|
-
ref.current.removeEventListener(eventType, handler)
|
|
44
|
-
}
|
|
45
|
-
}, [])
|
|
46
|
-
|
|
47
|
-
const clean = useCallback(() => {
|
|
48
|
-
if (ref.current) {
|
|
49
|
-
ref.current.cleanup()
|
|
50
|
-
}
|
|
51
|
-
}, [])
|
|
52
|
-
|
|
53
|
-
return [ref, { start, stop, abort, subscribe, unsubscribe, clean }]
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default useVocal
|
|
57
|
-
|
|
58
|
-
// TODO: Return the instance, not the ref
|
package/src/index.js
DELETED