@@ -9,145 +9,201 @@ import {
9
9
} from '../tests/utils/mock-fetch' ;
10
10
import { useChat } from './use-chat' ;
11
11
12
- const TestComponent = ( ) => {
13
- const [ id , setId ] = React . useState < string > ( 'first-id' ) ;
14
- const { messages, append, error, data, isLoading } = useChat ( { id } ) ;
15
-
16
- return (
17
- < div >
18
- < div data-testid = "loading" > { isLoading . toString ( ) } </ div >
19
- { error && < div data-testid = "error" > { error . toString ( ) } </ div > }
20
- { data && < div data-testid = "data" > { JSON . stringify ( data ) } </ div > }
21
- { messages . map ( ( m , idx ) => (
22
- < div data-testid = { `message-${ idx } ` } key = { m . id } >
23
- { m . role === 'user' ? 'User: ' : 'AI: ' }
24
- { m . content }
25
- </ div >
26
- ) ) }
27
-
28
- < button
29
- data-testid = "do-append"
30
- onClick = { ( ) => {
31
- append ( { role : 'user' , content : 'hi' } ) ;
32
- } }
33
- />
34
- < button
35
- data-testid = "do-change-id"
36
- onClick = { ( ) => {
37
- setId ( 'second-id' ) ;
38
- } }
39
- />
40
- </ div >
41
- ) ;
42
- } ;
43
-
44
- beforeEach ( ( ) => {
45
- render ( < TestComponent /> ) ;
46
- } ) ;
12
+ describe ( 'stream data stream' , ( ) => {
13
+ const TestComponent = ( ) => {
14
+ const [ id , setId ] = React . useState < string > ( 'first-id' ) ;
15
+ const { messages, append, error, data, isLoading } = useChat ( { id } ) ;
16
+
17
+ return (
18
+ < div >
19
+ < div data-testid = "loading" > { isLoading . toString ( ) } </ div >
20
+ { error && < div data-testid = "error" > { error . toString ( ) } </ div > }
21
+ { data && < div data-testid = "data" > { JSON . stringify ( data ) } </ div > }
22
+ { messages . map ( ( m , idx ) => (
23
+ < div data-testid = { `message-${ idx } ` } key = { m . id } >
24
+ { m . role === 'user' ? 'User: ' : 'AI: ' }
25
+ { m . content }
26
+ </ div >
27
+ ) ) }
28
+
29
+ < button
30
+ data-testid = "do-append"
31
+ onClick = { ( ) => {
32
+ append ( { role : 'user' , content : 'hi' } ) ;
33
+ } }
34
+ />
35
+ < button
36
+ data-testid = "do-change-id"
37
+ onClick = { ( ) => {
38
+ setId ( 'second-id' ) ;
39
+ } }
40
+ />
41
+ </ div >
42
+ ) ;
43
+ } ;
47
44
48
- afterEach ( ( ) => {
49
- vi . restoreAllMocks ( ) ;
50
- cleanup ( ) ;
51
- } ) ;
45
+ beforeEach ( ( ) => {
46
+ render ( < TestComponent /> ) ;
47
+ } ) ;
52
48
53
- test ( 'Shows streamed complex text response' , async ( ) => {
54
- mockFetchDataStream ( {
55
- url : 'https://example.com/api/chat' ,
56
- chunks : [ '0:"Hello"\n' , '0:","\n' , '0:" world"\n' , '0:"."\n' ] ,
49
+ afterEach ( ( ) => {
50
+ vi . restoreAllMocks ( ) ;
51
+ cleanup ( ) ;
57
52
} ) ;
58
53
59
- await userEvent . click ( screen . getByTestId ( 'do-append' ) ) ;
54
+ it ( 'should show streamed response' , async ( ) => {
55
+ mockFetchDataStream ( {
56
+ url : 'https://example.com/api/chat' ,
57
+ chunks : [ '0:"Hello"\n' , '0:","\n' , '0:" world"\n' , '0:"."\n' ] ,
58
+ } ) ;
60
59
61
- await screen . findByTestId ( 'message-0' ) ;
62
- expect ( screen . getByTestId ( 'message-0' ) ) . toHaveTextContent ( 'User: hi' ) ;
60
+ await userEvent . click ( screen . getByTestId ( 'do-append' ) ) ;
63
61
64
- await screen . findByTestId ( 'message-1' ) ;
65
- expect ( screen . getByTestId ( 'message-1' ) ) . toHaveTextContent (
66
- 'AI: Hello, world.' ,
67
- ) ;
68
- } ) ;
62
+ await screen . findByTestId ( 'message-0' ) ;
63
+ expect ( screen . getByTestId ( 'message-0' ) ) . toHaveTextContent ( 'User: hi' ) ;
69
64
70
- test ( 'Shows streamed complex text response with data' , async ( ) => {
71
- mockFetchDataStream ( {
72
- url : 'https://example.com/api/chat ',
73
- chunks : [ '2:[{"t1":"v1"}]\n' , '0:"Hello"\n' ] ,
65
+ await screen . findByTestId ( 'message-1' ) ;
66
+ expect ( screen . getByTestId ( 'message-1' ) ) . toHaveTextContent (
67
+ 'AI: Hello, world. ',
68
+ ) ;
74
69
} ) ;
75
70
76
- await userEvent . click ( screen . getByTestId ( 'do-append' ) ) ;
71
+ it ( 'should show streamed response with data' , async ( ) => {
72
+ mockFetchDataStream ( {
73
+ url : 'https://example.com/api/chat' ,
74
+ chunks : [ '2:[{"t1":"v1"}]\n' , '0:"Hello"\n' ] ,
75
+ } ) ;
77
76
78
- await screen . findByTestId ( 'data' ) ;
79
- expect ( screen . getByTestId ( 'data' ) ) . toHaveTextContent ( '[{"t1":"v1"}]' ) ;
77
+ await userEvent . click ( screen . getByTestId ( 'do-append' ) ) ;
80
78
81
- await screen . findByTestId ( 'message-1' ) ;
82
- expect ( screen . getByTestId ( 'message-1' ) ) . toHaveTextContent ( 'AI: Hello' ) ;
83
- } ) ;
79
+ await screen . findByTestId ( 'data' ) ;
80
+ expect ( screen . getByTestId ( 'data' ) ) . toHaveTextContent ( '[{"t1":"v1"}]' ) ;
84
81
85
- test ( 'Shows error response' , async ( ) => {
86
- mockFetchError ( { statusCode : 404 , errorMessage : 'Not found' } ) ;
82
+ await screen . findByTestId ( 'message-1' ) ;
83
+ expect ( screen . getByTestId ( 'message-1' ) ) . toHaveTextContent ( 'AI: Hello' ) ;
84
+ } ) ;
87
85
88
- await userEvent . click ( screen . getByTestId ( 'do-append' ) ) ;
86
+ it ( 'should show error response' , async ( ) => {
87
+ mockFetchError ( { statusCode : 404 , errorMessage : 'Not found' } ) ;
89
88
90
- // TODO bug? the user message does not show up
91
- // await screen.findByTestId('message-0');
92
- // expect(screen.getByTestId('message-0')).toHaveTextContent('User: hi');
89
+ await userEvent . click ( screen . getByTestId ( 'do-append' ) ) ;
93
90
94
- await screen . findByTestId ( 'error' ) ;
95
- expect ( screen . getByTestId ( 'error' ) ) . toHaveTextContent ( 'Error: Not found' ) ;
96
- } ) ;
91
+ // TODO bug? the user message does not show up
92
+ // await screen.findByTestId('message-0');
93
+ // expect(screen.getByTestId('message-0')).toHaveTextContent('User: hi');
94
+
95
+ await screen . findByTestId ( 'error' ) ;
96
+ expect ( screen . getByTestId ( 'error' ) ) . toHaveTextContent ( 'Error: Not found' ) ;
97
+ } ) ;
98
+
99
+ describe ( 'loading state' , ( ) => {
100
+ it ( 'should show loading state' , async ( ) => {
101
+ let finishGeneration : ( ( value ?: unknown ) => void ) | undefined ;
102
+ const finishGenerationPromise = new Promise ( resolve => {
103
+ finishGeneration = resolve ;
104
+ } ) ;
97
105
98
- describe ( 'loading state' , ( ) => {
99
- test ( 'should show loading state' , async ( ) => {
100
- let finishGeneration : ( ( value ?: unknown ) => void ) | undefined ;
101
- const finishGenerationPromise = new Promise ( resolve => {
102
- finishGeneration = resolve ;
106
+ mockFetchDataStreamWithGenerator ( {
107
+ url : 'https://example.com/api/chat' ,
108
+ chunkGenerator : ( async function * generate ( ) {
109
+ const encoder = new TextEncoder ( ) ;
110
+ yield encoder . encode ( '0:"Hello"\n' ) ;
111
+ await finishGenerationPromise ;
112
+ } ) ( ) ,
113
+ } ) ;
114
+
115
+ await userEvent . click ( screen . getByTestId ( 'do-append' ) ) ;
116
+
117
+ await screen . findByTestId ( 'loading' ) ;
118
+ expect ( screen . getByTestId ( 'loading' ) ) . toHaveTextContent ( 'true' ) ;
119
+
120
+ finishGeneration ?.( ) ;
121
+
122
+ await findByText ( await screen . findByTestId ( 'loading' ) , 'false' ) ;
123
+ expect ( screen . getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
103
124
} ) ;
104
125
105
- mockFetchDataStreamWithGenerator ( {
106
- url : 'https://example.com/api/chat' ,
107
- chunkGenerator : ( async function * generate ( ) {
108
- const encoder = new TextEncoder ( ) ;
109
- yield encoder . encode ( '0:"Hello"\n' ) ;
110
- await finishGenerationPromise ;
111
- } ) ( ) ,
126
+ it ( 'should reset loading state on error' , async ( ) => {
127
+ mockFetchError ( { statusCode : 404 , errorMessage : 'Not found' } ) ;
128
+
129
+ await userEvent . click ( screen . getByTestId ( 'do-append' ) ) ;
130
+
131
+ await screen . findByTestId ( 'loading' ) ;
132
+ expect ( screen . getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
112
133
} ) ;
134
+ } ) ;
113
135
114
- await userEvent . click ( screen . getByTestId ( 'do-append' ) ) ;
136
+ describe ( 'id' , ( ) => {
137
+ it ( 'should clear out messages when the id changes' , async ( ) => {
138
+ mockFetchDataStream ( {
139
+ url : 'https://example.com/api/chat' ,
140
+ chunks : [ '0:"Hello"\n' , '0:","\n' , '0:" world"\n' , '0:"."\n' ] ,
141
+ } ) ;
115
142
116
- await screen . findByTestId ( 'loading' ) ;
117
- expect ( screen . getByTestId ( 'loading' ) ) . toHaveTextContent ( 'true' ) ;
143
+ await userEvent . click ( screen . getByTestId ( 'do-append' ) ) ;
118
144
119
- finishGeneration ?.( ) ;
145
+ await screen . findByTestId ( 'message-1' ) ;
146
+ expect ( screen . getByTestId ( 'message-1' ) ) . toHaveTextContent (
147
+ 'AI: Hello, world.' ,
148
+ ) ;
120
149
121
- await findByText ( await screen . findByTestId ( 'loading' ) , 'false' ) ;
122
- expect ( screen . getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
150
+ await userEvent . click ( screen . getByTestId ( 'do-change-id' ) ) ;
151
+
152
+ expect ( screen . queryByTestId ( 'message-0' ) ) . not . toBeInTheDocument ( ) ;
153
+ } ) ;
123
154
} ) ;
155
+ } ) ;
124
156
125
- test ( 'should reset loading state on error' , async ( ) => {
126
- mockFetchError ( { statusCode : 404 , errorMessage : 'Not found' } ) ;
157
+ describe ( 'text stream' , ( ) => {
158
+ const TestComponent = ( ) => {
159
+ const { messages, append } = useChat ( {
160
+ streamMode : 'text' ,
161
+ } ) ;
127
162
128
- await userEvent . click ( screen . getByTestId ( 'do-append' ) ) ;
163
+ return (
164
+ < div >
165
+ { messages . map ( ( m , idx ) => (
166
+ < div data-testid = { `message-${ idx } -text-stream` } key = { m . id } >
167
+ { m . role === 'user' ? 'User: ' : 'AI: ' }
168
+ { m . content }
169
+ </ div >
170
+ ) ) }
171
+
172
+ < button
173
+ data-testid = "do-append-text-stream"
174
+ onClick = { ( ) => {
175
+ append ( { role : 'user' , content : 'hi' } ) ;
176
+ } }
177
+ />
178
+ </ div >
179
+ ) ;
180
+ } ;
129
181
130
- await screen . findByTestId ( 'loading' ) ;
131
- expect ( screen . getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
182
+ beforeEach ( ( ) => {
183
+ render ( < TestComponent /> ) ;
132
184
} ) ;
133
- } ) ;
134
185
135
- describe ( 'id' , ( ) => {
136
- it ( 'should clear out messages when the id changes' , async ( ) => {
186
+ afterEach ( ( ) => {
187
+ vi . restoreAllMocks ( ) ;
188
+ cleanup ( ) ;
189
+ } ) ;
190
+
191
+ it ( 'should show streamed response' , async ( ) => {
137
192
mockFetchDataStream ( {
138
193
url : 'https://example.com/api/chat' ,
139
- chunks : [ '0:" Hello"\n ' , '0:","\n ' , '0:" world"\n ' , '0:"."\n ' ] ,
194
+ chunks : [ 'Hello' , ', ' , ' world' , '. ' ] ,
140
195
} ) ;
141
196
142
- await userEvent . click ( screen . getByTestId ( 'do-append' ) ) ;
197
+ await userEvent . click ( screen . getByTestId ( 'do-append-text-stream ' ) ) ;
143
198
144
- await screen . findByTestId ( 'message-1 ' ) ;
145
- expect ( screen . getByTestId ( 'message-1 ' ) ) . toHaveTextContent (
146
- 'AI: Hello, world. ' ,
199
+ await screen . findByTestId ( 'message-0-text-stream ' ) ;
200
+ expect ( screen . getByTestId ( 'message-0-text-stream ' ) ) . toHaveTextContent (
201
+ 'User: hi ' ,
147
202
) ;
148
203
149
- await userEvent . click ( screen . getByTestId ( 'do-change-id' ) ) ;
150
-
151
- expect ( screen . queryByTestId ( 'message-0' ) ) . not . toBeInTheDocument ( ) ;
204
+ await screen . findByTestId ( 'message-1-text-stream' ) ;
205
+ expect ( screen . getByTestId ( 'message-1-text-stream' ) ) . toHaveTextContent (
206
+ 'AI: Hello, world.' ,
207
+ ) ;
152
208
} ) ;
153
209
} ) ;
0 commit comments