@@ -2,9 +2,17 @@ import { render } from 'ink-testing-library';
22
33import { Chat } from './Chat' ;
44
5+ vi . mock ( '../utils/ollama' , ( ) => ( {
6+ streamChat : vi . fn ( ) . mockImplementation ( function * ( ) {
7+ yield 'Mocked' ;
8+ yield ' response' ;
9+ } ) ,
10+ } ) ) ;
11+
512const ENTER = '\r' ;
613
7- const tick = ( ) => new Promise < void > ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
14+ const tick = ( ms = 0 ) =>
15+ new Promise < void > ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
816
917type Stdin = ReturnType < typeof render > [ 'stdin' ] ;
1018
@@ -15,6 +23,11 @@ async function typeText(stdin: Stdin, text: string) {
1523 }
1624}
1725
26+ async function waitForStream ( ) {
27+ // Allow time for async generator to yield values
28+ await tick ( 10 ) ;
29+ }
30+
1831describe ( 'Chat' , ( ) => {
1932 it ( 'renders input prompt' , ( ) => {
2033 const { lastFrame } = render ( < Chat /> ) ;
@@ -25,19 +38,20 @@ describe('Chat', () => {
2538 const { lastFrame, stdin } = render ( < Chat /> ) ;
2639 await typeText ( stdin , 'hello' ) ;
2740 stdin . write ( ENTER ) ;
28- await tick ( ) ;
41+ await waitForStream ( ) ;
2942 expect ( lastFrame ( ) ) . toContain ( 'hello' ) ;
3043 } ) ;
3144
3245 it ( 'clears input after submit' , async ( ) => {
3346 const { lastFrame, stdin } = render ( < Chat /> ) ;
3447 await typeText ( stdin , 'hello' ) ;
3548 stdin . write ( ENTER ) ;
36- await tick ( ) ;
49+ await waitForStream ( ) ;
3750 const frame = lastFrame ( ) ?? '' ;
38- const inputLine =
39- frame . split ( '\n' ) . find ( ( line ) => line . includes ( '>' ) ) ?? '' ;
40- expect ( inputLine . replace ( '>' , '' ) . trim ( ) ) . toBe ( '' ) ;
51+ // Find the last line that contains just the prompt (no user text after >)
52+ const lines = frame . split ( '\n' ) ;
53+ const inputLine = lines . find ( ( line ) => line . trim ( ) === '>' ) ?? '' ;
54+ expect ( inputLine . trim ( ) ) . toBe ( '>' ) ;
4155 } ) ;
4256
4357 it ( 'does not add blank messages' , async ( ) => {
@@ -56,14 +70,51 @@ describe('Chat', () => {
5670 const { lastFrame, stdin } = render ( < Chat /> ) ;
5771 await typeText ( stdin , 'first' ) ;
5872 stdin . write ( ENTER ) ;
59- await tick ( ) ;
73+ await waitForStream ( ) ;
6074 await typeText ( stdin , 'second' ) ;
6175 stdin . write ( ENTER ) ;
62- await tick ( ) ;
76+ await waitForStream ( ) ;
6377 const frame = lastFrame ( ) ?? '' ;
6478 const firstIdx = frame . indexOf ( 'first' ) ;
6579 const secondIdx = frame . indexOf ( 'second' ) ;
6680 expect ( firstIdx ) . toBeGreaterThanOrEqual ( 0 ) ;
6781 expect ( secondIdx ) . toBeGreaterThan ( firstIdx ) ;
6882 } ) ;
6983} ) ;
84+
85+ describe ( 'Chat with error' , ( ) => {
86+ it ( 'shows error message when stream fails with Error' , async ( ) => {
87+ const { streamChat } = await import ( '../utils/ollama' ) ;
88+ vi . mocked ( streamChat ) . mockImplementationOnce ( async function * ( ) {
89+ await Promise . resolve ( ) ;
90+ yield '' ;
91+ throw new Error ( 'Connection failed' ) ;
92+ } ) ;
93+
94+ const { lastFrame, stdin } = render ( < Chat /> ) ;
95+
96+ await typeText ( stdin , 'hello' ) ;
97+ stdin . write ( ENTER ) ;
98+ await waitForStream ( ) ;
99+
100+ expect ( lastFrame ( ) ) . toContain ( 'Error: Connection failed' ) ;
101+ } ) ;
102+
103+ it ( 'shows error message when stream fails with non-Error' , async ( ) => {
104+ const { streamChat } = await import ( '../utils/ollama' ) ;
105+ vi . mocked ( streamChat ) . mockImplementationOnce ( async function * ( ) {
106+ await Promise . resolve ( ) ;
107+ yield '' ;
108+ // eslint-disable-next-line @typescript-eslint/only-throw-error
109+ throw { toString : ( ) => 'Custom error' } ;
110+ } ) ;
111+
112+ const { lastFrame, stdin } = render ( < Chat /> ) ;
113+
114+ await typeText ( stdin , 'hello' ) ;
115+ stdin . write ( ENTER ) ;
116+ await waitForStream ( ) ;
117+
118+ expect ( lastFrame ( ) ) . toContain ( 'Error: Custom error' ) ;
119+ } ) ;
120+ } ) ;
0 commit comments