@untemps/react-vocal 2.0.0-beta.1 → 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.
Files changed (42) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +54 -20
  3. package/dist/index.es.js +330 -1914
  4. package/dist/index.js +2 -4
  5. package/package.json +14 -7
  6. package/.github/workflows/publish.yml +0 -32
  7. package/.husky/commit-msg +0 -1
  8. package/.husky/pre-commit +0 -1
  9. package/.prettierignore +0 -3
  10. package/.prettierrc +0 -29
  11. package/CLAUDE.md +0 -59
  12. package/assets/icon-idle.png +0 -0
  13. package/assets/icon-listening.png +0 -0
  14. package/assets/microphone.png +0 -0
  15. package/assets/react-vocal.png +0 -0
  16. package/commitlint.config.js +0 -7
  17. package/dev/index.html +0 -24
  18. package/dev/package.json +0 -18
  19. package/dev/public/index.html +0 -24
  20. package/dev/src/index.jsx +0 -45
  21. package/dev/vite.config.js +0 -10
  22. package/dev/yarn.lock +0 -201
  23. package/dist/index.es.js.map +0 -1
  24. package/dist/index.js.map +0 -1
  25. package/dist/index.umd.js +0 -9
  26. package/dist/index.umd.js.map +0 -1
  27. package/src/components/Icon.jsx +0 -24
  28. package/src/components/Vocal.jsx +0 -168
  29. package/src/components/__tests__/Icon.test.jsx +0 -38
  30. package/src/components/__tests__/Vocal.test.jsx +0 -270
  31. package/src/components/__tests__/VocalWithMockedUseVocal.test.jsx +0 -38
  32. package/src/components/__tests__/__snapshots__/Icon.test.jsx.snap +0 -21
  33. package/src/components/__tests__/__snapshots__/Vocal.test.jsx.snap +0 -28
  34. package/src/hooks/__tests__/useCommands.test.js +0 -64
  35. package/src/hooks/__tests__/useTimeout.test.js +0 -69
  36. package/src/hooks/__tests__/useVocal.test.js +0 -197
  37. package/src/hooks/useCommands.js +0 -21
  38. package/src/hooks/useTimeout.js +0 -21
  39. package/src/hooks/useVocal.js +0 -56
  40. package/src/index.js +0 -7
  41. package/vite.config.js +0 -35
  42. package/vitest.setup.js +0 -71
@@ -1,168 +0,0 @@
1
- import React, { cloneElement, isValidElement, useRef, useState } from 'react'
2
- import { Vocal as SpeechRecognitionWrapper } from '@untemps/vocal'
3
- import { isFunction } from '@untemps/utils/function/isFunction'
4
-
5
- import useVocal from '../hooks/useVocal'
6
- import useTimeout from '../hooks/useTimeout'
7
- import useCommands from '../hooks/useCommands'
8
-
9
- import Icon from './Icon'
10
-
11
- const Vocal = ({
12
- children,
13
- commands = null,
14
- lang = 'en-US',
15
- grammars = null,
16
- timeout = 3000,
17
- ariaLabel = 'start recognition',
18
- style = null,
19
- className = null,
20
- outlineStyle = '2px solid',
21
- onStart = null,
22
- onEnd = null,
23
- onSpeechStart = null,
24
- onSpeechEnd = null,
25
- onResult = null,
26
- onError = null,
27
- onNoMatch = null,
28
- __rsInstance,
29
- }) => {
30
- const buttonRef = useRef(null)
31
- const [isListening, setIsListening] = useState(false)
32
-
33
- const [, { start, stop, subscribe, unsubscribe }] = useVocal(lang, grammars, __rsInstance)
34
- const triggerCommand = useCommands(commands)
35
-
36
- const _onEnd = (e) => {
37
- stopTimer()
38
- stopRecognition()
39
- unsubscribeAll()
40
- onEnd?.(e)
41
- }
42
-
43
- const [startTimer, stopTimer] = useTimeout(_onEnd, timeout)
44
-
45
- const startRecognition = () => {
46
- try {
47
- setIsListening(true)
48
- subscribeAll()
49
- start()
50
- } catch (error) {
51
- _onError(error)
52
- }
53
- }
54
-
55
- const stopRecognition = () => {
56
- try {
57
- setIsListening(false)
58
- stop()
59
- } catch (error) {
60
- onError?.(error)
61
- }
62
- }
63
-
64
- const _onFocus = () => {
65
- if (!className && outlineStyle) {
66
- buttonRef.current.style.outline = outlineStyle
67
- }
68
- }
69
-
70
- const _onBlur = () => {
71
- if (!className && outlineStyle) {
72
- buttonRef.current.style.outline = 'none'
73
- }
74
- }
75
-
76
- const _onStart = (e) => {
77
- startTimer()
78
- onStart?.(e)
79
- }
80
-
81
- const _onSpeechStart = (e) => {
82
- stopTimer()
83
- onSpeechStart?.(e)
84
- }
85
-
86
- const _onSpeechEnd = (e) => {
87
- startTimer()
88
- onSpeechEnd?.(e)
89
- }
90
-
91
- const _onResult = (event, result) => {
92
- stopTimer()
93
- stopRecognition()
94
- triggerCommand(result)
95
- onResult?.(result, event)
96
- }
97
-
98
- const _onError = (error) => {
99
- stopRecognition()
100
- onError?.(error)
101
- }
102
-
103
- const _onNoMatch = (e) => {
104
- stopTimer()
105
- stopRecognition()
106
- onNoMatch?.(e)
107
- }
108
-
109
- const HANDLERS = {
110
- start: _onStart,
111
- end: _onEnd,
112
- speechstart: _onSpeechStart,
113
- speechend: _onSpeechEnd,
114
- result: _onResult,
115
- error: _onError,
116
- nomatch: _onNoMatch,
117
- }
118
-
119
- const subscribeAll = () => Object.entries(HANDLERS).forEach(([event, handler]) => subscribe(event, handler))
120
- const unsubscribeAll = () => Object.entries(HANDLERS).forEach(([event, handler]) => unsubscribe(event, handler))
121
-
122
- const _renderDefault = () => (
123
- <button
124
- data-testid="__vocal-root__"
125
- ref={buttonRef}
126
- role="button"
127
- aria-label={ariaLabel}
128
- style={
129
- className
130
- ? null
131
- : {
132
- width: 24,
133
- height: 24,
134
- backgroundColor: 'transparent', // `background: none` shorthand resets all sub-properties; jsdom 29 + jest-dom v6 don't reflect that correctly via getComputedStyle
135
- border: 'none',
136
- padding: 0,
137
- cursor: !isListening ? 'pointer' : 'default',
138
- ...style,
139
- }
140
- }
141
- className={className}
142
- onFocus={_onFocus}
143
- onBlur={_onBlur}
144
- onClick={startRecognition}
145
- >
146
- <Icon isActive={isListening} color="#aaa" />
147
- </button>
148
- )
149
-
150
- const _renderChildren = () => {
151
- if (SpeechRecognitionWrapper.isSupported) {
152
- if (isFunction(children)) {
153
- return children(startRecognition, stopRecognition, isListening)
154
- } else if (isValidElement(children)) {
155
- return cloneElement(children, {
156
- ...(!isListening && { onClick: startRecognition }),
157
- })
158
- } else {
159
- return _renderDefault()
160
- }
161
- }
162
- return null
163
- }
164
-
165
- return _renderChildren()
166
- }
167
-
168
- export default Vocal
@@ -1,38 +0,0 @@
1
- import React from 'react'
2
- import { render } from '@testing-library/react'
3
-
4
- import Icon from '../Icon'
5
-
6
- const defaultProps = {}
7
- const getInstance = (props = {}) => <Icon {...defaultProps} {...props} />
8
-
9
- describe('Icon', () => {
10
- it('matches snapshot', () => {
11
- const { asFragment } = render(getInstance())
12
- expect(asFragment()).toMatchSnapshot()
13
- })
14
-
15
- it('renders component', () => {
16
- const { queryByTestId } = render(getInstance())
17
- expect(queryByTestId('__icon-root__')).toBeInTheDocument()
18
- })
19
-
20
- it('renders component color', () => {
21
- const color = 'green'
22
- const { queryByTestId } = render(getInstance({ color }))
23
- expect(queryByTestId('__icon-path__')).toHaveAttribute('fill', color)
24
- })
25
-
26
- it('renders active component', () => {
27
- const isActive = true
28
- const { queryByTestId } = render(getInstance({ isActive }))
29
- expect(queryByTestId('__icon-active__')).toBeInTheDocument()
30
- })
31
-
32
- it('renders active component color', () => {
33
- const isActive = true
34
- const activeColor = 'blue'
35
- const { queryByTestId } = render(getInstance({ isActive, activeColor }))
36
- expect(queryByTestId('__icon-active__')).toHaveAttribute('fill', activeColor)
37
- })
38
- })
@@ -1,270 +0,0 @@
1
- import React from 'react'
2
- import { waitFor } from '@testing-library/dom'
3
- import { act, fireEvent, render } from '@testing-library/react'
4
- import { Vocal as SpeechRecognitionWrapper } from '@untemps/vocal'
5
-
6
- import Vocal from '../Vocal'
7
-
8
- const defaultProps = {}
9
- const getInstance = (props = {}, children = null) => (
10
- <Vocal {...defaultProps} {...props}>
11
- {children}
12
- </Vocal>
13
- )
14
-
15
- describe('Vocal', () => {
16
- it('matches snapshot', () => {
17
- const { asFragment } = render(getInstance())
18
- expect(asFragment()).toMatchSnapshot()
19
- })
20
-
21
- it('renders default children', () => {
22
- const { queryByTestId } = render(getInstance())
23
- expect(queryByTestId('__vocal-root__')).toBeInTheDocument()
24
- })
25
-
26
- it('renders custom children element', () => {
27
- const { queryByTestId } = render(getInstance(null, <div data-testid="__vocal-custom-root__" />))
28
- expect(queryByTestId('__vocal-root__')).not.toBeInTheDocument()
29
- expect(queryByTestId('__vocal-custom-root__')).toBeInTheDocument()
30
- })
31
-
32
- it('renders no children element if SpeechRecognition is not supported', () => {
33
- vi.spyOn(SpeechRecognitionWrapper, 'isSupported', 'get').mockReturnValueOnce(false)
34
- const { queryByTestId } = render(getInstance(null, <div data-testid="__vocal-custom-root__" />))
35
- expect(queryByTestId('__vocal-root__')).not.toBeInTheDocument()
36
- expect(queryByTestId('__vocal-custom-root__')).not.toBeInTheDocument()
37
- vi.clearAllMocks()
38
- })
39
-
40
- it('renders custom children function', () => {
41
- const { queryByTestId } = render(getInstance(null, () => <div data-testid="__vocal-custom-root__" />))
42
- expect(queryByTestId('__vocal-root__')).not.toBeInTheDocument()
43
- expect(queryByTestId('__vocal-custom-root__')).toBeInTheDocument()
44
- })
45
-
46
- it('starts recognition with custom children function', async () => {
47
- const onStart = vi.fn()
48
- const { queryByTestId } = render(
49
- getInstance({ onStart }, (start) => <div data-testid="__vocal-custom-root__" onClick={start} />)
50
- )
51
- await act(async () => {
52
- fireEvent.click(queryByTestId('__vocal-custom-root__'))
53
- await waitFor(() => expect(onStart).toHaveBeenCalled())
54
- })
55
- })
56
-
57
- it('stops recognition with custom children function', async () => {
58
- const onEnd = vi.fn()
59
- const { queryByText } = render(
60
- getInstance({ onEnd }, (start, stop) => (
61
- <div data-testid="__vocal-custom-root__">
62
- <button onClick={start}>start</button>
63
- <button onClick={stop}>stop</button>
64
- </div>
65
- ))
66
- )
67
- await act(async () => {
68
- fireEvent.click(queryByText('start'))
69
- fireEvent.click(queryByText('stop'))
70
- await waitFor(() => expect(onEnd).toHaveBeenCalled())
71
- })
72
- })
73
-
74
- it('gets recognition status with custom children function', async () => {
75
- const onEnd = vi.fn()
76
- const { queryByText } = render(
77
- getInstance({ onEnd }, (start, stop, isStarted) => (
78
- <div data-testid="__vocal-custom-root__">
79
- <div>{isStarted ? 'Started' : 'Stopped'}</div>
80
- <button onClick={start}>start</button>
81
- <button onClick={stop}>stop</button>
82
- </div>
83
- ))
84
- )
85
- await act(async () => {
86
- fireEvent.click(queryByText('start'))
87
- await waitFor(() => {
88
- expect(queryByText('Started')).toBeInTheDocument()
89
- })
90
- fireEvent.click(queryByText('stop'))
91
- await waitFor(() => {
92
- expect(queryByText('Stopped')).toBeInTheDocument()
93
- })
94
- })
95
- })
96
-
97
- it('renders pointer cursor when idle', () => {
98
- const { getByTestId } = render(getInstance())
99
- expect(getByTestId('__vocal-root__')).toHaveStyle({ cursor: 'pointer' })
100
- })
101
-
102
- it('renders default cursor when listening', () => {
103
- const { getByTestId } = render(getInstance())
104
- fireEvent.click(getByTestId('__vocal-root__'))
105
- expect(getByTestId('__vocal-root__')).toHaveStyle({ cursor: 'default' })
106
- })
107
-
108
- it('renders outline when focused', () => {
109
- const { getByTestId } = render(getInstance())
110
- fireEvent.focus(getByTestId('__vocal-root__'))
111
- expect(getByTestId('__vocal-root__')).toHaveStyle({ outline: '2px solid' })
112
- })
113
-
114
- it('remove outline when blurred', () => {
115
- const { getByTestId } = render(getInstance())
116
- fireEvent.blur(getByTestId('__vocal-root__'))
117
- expect(getByTestId('__vocal-root__')).toHaveStyle({ outline: 'none' })
118
- })
119
-
120
- it('not uses style when className is set', () => {
121
- const { getByTestId } = render(getInstance({ className: 'foo' }))
122
- expect(getByTestId('__vocal-root__')).not.toHaveStyle({ cursor: 'pointer' })
123
- })
124
-
125
- it('uses custom styles', () => {
126
- const { getByTestId } = render(getInstance({ style: { backgroundColor: 'blue' } }))
127
- // jest-dom v6 + jsdom 29: `div.style.color = 'blue'` reads back as 'blue' but getComputedStyle returns RGB; normalisation no longer bridges the gap
128
- expect(getByTestId('__vocal-root__')).toHaveStyle({ backgroundColor: 'rgb(0, 0, 255)' })
129
- })
130
-
131
- it('responds to command', async () => {
132
- const callback = vi.fn()
133
- const recognition = new SpeechRecognitionWrapper()
134
- const commands = { foo: callback }
135
- const { getByTestId } = render(getInstance({ __rsInstance: recognition, commands }))
136
-
137
- let flag = false
138
- recognition.addEventListener('start', async () => {
139
- flag = true
140
- })
141
-
142
- await act(async () => {
143
- fireEvent.click(getByTestId('__vocal-root__'))
144
-
145
- await waitFor(() => flag)
146
-
147
- recognition.instance.say('Foo')
148
- await waitFor(() => expect(callback).toHaveBeenCalledWith('Foo'))
149
- })
150
- })
151
-
152
- it('triggers onStart handler', async () => {
153
- const onStart = vi.fn()
154
- const { queryByTestId } = render(getInstance({ onStart }))
155
- await act(async () => {
156
- fireEvent.click(queryByTestId('__vocal-root__'))
157
- await waitFor(() => expect(onStart).toHaveBeenCalled())
158
- })
159
- })
160
-
161
- it('triggers onResult handler', async () => {
162
- const onResult = vi.fn()
163
- const recognition = new SpeechRecognitionWrapper()
164
- const { getByTestId } = render(getInstance({ __rsInstance: recognition, onResult }))
165
-
166
- let flag = false
167
- recognition.addEventListener('start', async () => {
168
- flag = true
169
- })
170
-
171
- await act(async () => {
172
- fireEvent.click(getByTestId('__vocal-root__'))
173
-
174
- await waitFor(() => flag)
175
-
176
- recognition.instance.say('Foo')
177
- await waitFor(() => expect(onResult).toHaveBeenCalledWith('Foo', expect.anything()))
178
- })
179
- })
180
-
181
- it('triggers onNoMatch handler', async () => {
182
- const onNoMatch = vi.fn()
183
- const recognition = new SpeechRecognitionWrapper()
184
- const { getByTestId } = render(getInstance({ __rsInstance: recognition, onNoMatch }))
185
-
186
- let flag = false
187
- recognition.addEventListener('start', async () => {
188
- flag = true
189
- })
190
-
191
- await act(async () => {
192
- fireEvent.click(getByTestId('__vocal-root__'))
193
-
194
- await waitFor(() => flag)
195
-
196
- recognition.instance.say(null)
197
- await waitFor(() => expect(onNoMatch).toHaveBeenCalled())
198
- })
199
- })
200
-
201
- it('triggers onSpeechStart handler', async () => {
202
- const onSpeechStart = vi.fn()
203
- const recognition = new SpeechRecognitionWrapper()
204
- const { getByTestId } = render(getInstance({ __rsInstance: recognition, onSpeechStart }))
205
-
206
- let flag = false
207
- recognition.addEventListener('start', async () => {
208
- flag = true
209
- })
210
-
211
- await act(async () => {
212
- fireEvent.click(getByTestId('__vocal-root__'))
213
-
214
- await waitFor(() => flag)
215
-
216
- recognition.instance.say('Foo')
217
- await waitFor(() => expect(onSpeechStart).toHaveBeenCalled())
218
- })
219
- })
220
-
221
- it('triggers onSpeechEnd handler', async () => {
222
- const onSpeechEnd = vi.fn()
223
- const recognition = new SpeechRecognitionWrapper()
224
- const { getByTestId } = render(getInstance({ __rsInstance: recognition, onSpeechEnd }))
225
-
226
- let flag = false
227
- recognition.addEventListener('start', async () => {
228
- flag = true
229
- })
230
-
231
- await act(async () => {
232
- fireEvent.click(getByTestId('__vocal-root__'))
233
-
234
- await waitFor(() => flag)
235
-
236
- recognition.instance.say('Foo')
237
- await waitFor(() => expect(onSpeechEnd).toHaveBeenCalled())
238
- })
239
- })
240
-
241
- it('triggers onEnd handler after timeout', async () => {
242
- const timeout = 100
243
- const onEnd = vi.fn()
244
- const { getByTestId } = render(getInstance({ timeout, onEnd }))
245
- await act(async () => {
246
- fireEvent.click(getByTestId('__vocal-root__'))
247
- await waitFor(() => expect(onEnd).toHaveBeenCalled(), { timeout: timeout * 2 })
248
- })
249
- })
250
-
251
- it('triggers onEnd handler after speech', async () => {
252
- const onEnd = vi.fn()
253
- const recognition = new SpeechRecognitionWrapper()
254
- const { getByTestId } = render(getInstance({ __rsInstance: recognition, onEnd }))
255
-
256
- let flag = false
257
- recognition.addEventListener('start', async () => {
258
- flag = true
259
- })
260
-
261
- await act(async () => {
262
- fireEvent.click(getByTestId('__vocal-root__'))
263
-
264
- await waitFor(() => flag)
265
-
266
- recognition.instance.say('Foo')
267
- await waitFor(() => expect(onEnd).toHaveBeenCalled())
268
- })
269
- })
270
- })
@@ -1,38 +0,0 @@
1
- import React from 'react'
2
- import { waitFor } from '@testing-library/dom'
3
- import { act, fireEvent, render } from '@testing-library/react'
4
-
5
- import Vocal from '../Vocal'
6
-
7
- vi.mock('../../hooks/useVocal', () => {
8
- return {
9
- default: () => [
10
- null,
11
- {
12
- subscribe: () => {
13
- throw new Error('Foo')
14
- },
15
- },
16
- ],
17
- }
18
- })
19
-
20
- const defaultProps = {}
21
- const getInstance = (props = {}, children = null) => (
22
- <Vocal {...defaultProps} {...props}>
23
- {children}
24
- </Vocal>
25
- )
26
-
27
- describe('Vocal', () => {
28
- it('triggers onError handler', async () => {
29
- const onError = vi.fn()
30
- const { queryByTestId } = render(getInstance({ onError }))
31
- await act(async () => {
32
- fireEvent.click(queryByTestId('__vocal-root__'))
33
- await waitFor(() => expect(onError).toHaveBeenCalled())
34
- })
35
- })
36
- })
37
-
38
- // TODO: Merge this file with Vocal.test.js
@@ -1,21 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`Icon > matches snapshot 1`] = `
4
- <DocumentFragment>
5
- <svg
6
- data-testid="__icon-root__"
7
- height="100%"
8
- viewBox="0 0 24 24"
9
- width="100%"
10
- xmlns="http://www.w3.org/2000/svg"
11
- >
12
- <g>
13
- <path
14
- d="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z"
15
- data-testid="__icon-path__"
16
- fill="black"
17
- />
18
- </g>
19
- </svg>
20
- </DocumentFragment>
21
- `;
@@ -1,28 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`Vocal > matches snapshot 1`] = `
4
- <DocumentFragment>
5
- <button
6
- aria-label="start recognition"
7
- data-testid="__vocal-root__"
8
- role="button"
9
- style="width: 24px; height: 24px; background-color: transparent; border: medium; padding: 0px; cursor: pointer;"
10
- >
11
- <svg
12
- data-testid="__icon-root__"
13
- height="100%"
14
- viewBox="0 0 24 24"
15
- width="100%"
16
- xmlns="http://www.w3.org/2000/svg"
17
- >
18
- <g>
19
- <path
20
- d="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z"
21
- data-testid="__icon-path__"
22
- fill="#aaa"
23
- />
24
- </g>
25
- </svg>
26
- </button>
27
- </DocumentFragment>
28
- `;
@@ -1,64 +0,0 @@
1
- import { renderHook } from '@testing-library/react'
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'
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 = vi.fn()
14
- renderHook(() => useTimeout(handler))
15
- expect(handler).not.toHaveBeenCalled()
16
- })
17
-
18
- it('not triggers handler before timeout', async () => {
19
- const handler = vi.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 = vi.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 = vi.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 = vi.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
- })