@@ -12,7 +12,7 @@ import { Host } from '../host';
1212import { startDevServer } from './start-devserver' ;
1313import { stopDevserver } from './stop-devserver' ;
1414import { McpToolContext } from './tool-registry' ;
15- import { waitForDevserverBuild } from './wait-for-devserver-build' ;
15+ import { WATCH_DELAY , waitForDevserverBuild } from './wait-for-devserver-build' ;
1616
1717class MockChildProcess extends EventEmitter {
1818 stdout = new EventEmitter ( ) ;
@@ -55,17 +55,12 @@ describe('Serve Tools', () => {
5555 expect ( mockProcess . kill ) . toHaveBeenCalled ( ) ;
5656 } ) ;
5757
58- it ( 'should find a free port if none is provided' , async ( ) => {
59- await startDevServer ( { } , mockContext , mockHost ) ;
60- expect ( mockHost . getAvailablePort ) . toHaveBeenCalled ( ) ;
61- } ) ;
62-
6358 it ( 'should wait for a build to complete' , async ( ) => {
6459 await startDevServer ( { } , mockContext , mockHost ) ;
6560
6661 const waitPromise = waitForDevserverBuild ( { timeout : 10 } , mockContext ) ;
6762
68- // Simulate build logs
63+ // Simulate build logs.
6964 mockProcess . stdout . emit ( 'data' , '... building ...' ) ;
7065 mockProcess . stdout . emit ( 'data' , '✔ Changes detected. Rebuilding...' ) ;
7166 mockProcess . stdout . emit ( 'data' , '... more logs ...' ) ;
@@ -74,28 +69,28 @@ describe('Serve Tools', () => {
7469 const waitResult = await waitPromise ;
7570 expect ( waitResult . structuredContent . status ) . toBe ( 'success' ) ;
7671 expect ( waitResult . structuredContent . logs ) . toEqual ( [
72+ '... building ...' ,
7773 '✔ Changes detected. Rebuilding...' ,
7874 '... more logs ...' ,
7975 'Application bundle generation complete.' ,
8076 ] ) ;
8177 } ) ;
8278
8379 it ( 'should handle multiple dev servers' , async ( ) => {
84- // Start server for project 1
80+ // Start server for project 1. This uses the basic mockProcess created for the tests.
8581 const startResult1 = await startDevServer ( { project : 'app-one' } , mockContext , mockHost ) ;
8682 expect ( startResult1 . structuredContent . message ) . toBe (
8783 `Development server for project 'app-one' started and watching for workspace changes.` ,
8884 ) ;
8985 const process1 = mockProcess ;
9086
91- // Start server for project 2
92- mockProcess = new MockChildProcess ( ) ;
93- ( mockHost . spawn as jasmine . Spy ) . and . returnValue ( mockProcess as unknown as ChildProcess ) ;
87+ // Start server for project 2, returning a new mock process.
88+ const process2 = new MockChildProcess ( ) ;
89+ ( mockHost . spawn as jasmine . Spy ) . and . returnValue ( process2 as unknown as ChildProcess ) ;
9490 const startResult2 = await startDevServer ( { project : 'app-two' } , mockContext , mockHost ) ;
9591 expect ( startResult2 . structuredContent . message ) . toBe (
9692 `Development server for project 'app-two' started and watching for workspace changes.` ,
9793 ) ;
98- const process2 = mockProcess ;
9994
10095 expect ( mockHost . spawn ) . toHaveBeenCalledWith ( 'ng' , [ 'serve' , 'app-one' , '--port=12345' ] , {
10196 stdio : 'pipe' ,
@@ -122,10 +117,14 @@ describe('Serve Tools', () => {
122117
123118 it ( 'should handle server crash' , async ( ) => {
124119 await startDevServer ( { project : 'crash-app' } , mockContext , mockHost ) ;
125- mockProcess . emit ( 'close' , 1 ) ; // Simulate a crash with exit code 1
120+
121+ // Simulate a crash with exit code 1
122+ mockProcess . stdout . emit ( 'data' , 'Fatal error.' ) ;
123+ mockProcess . emit ( 'close' , 1 ) ;
126124
127125 const stopResult = stopDevserver ( { project : 'crash-app' } , mockContext ) ;
128- expect ( stopResult . structuredContent . message ) . toContain ( 'is not running' ) ;
126+ expect ( stopResult . structuredContent . message ) . toContain ( 'stopped' ) ;
127+ expect ( stopResult . structuredContent . logs ) . toEqual ( [ 'Fatal error.' ] ) ;
129128 } ) ;
130129
131130 it ( 'wait should timeout if build takes too long' , async ( ) => {
@@ -136,4 +135,38 @@ describe('Serve Tools', () => {
136135 ) ;
137136 expect ( waitResult . structuredContent . status ) . toBe ( 'timeout' ) ;
138137 } ) ;
138+
139+ it ( 'should wait through multiple cycles for a build to complete' , async ( ) => {
140+ jasmine . clock ( ) . install ( ) ;
141+ try {
142+ await startDevServer ( { } , mockContext , mockHost ) ;
143+
144+ // Immediately simulate a build starting so isBuilding() is true.
145+ mockProcess . stdout . emit ( 'data' , '❯ Changes detected. Rebuilding...' ) ;
146+
147+ const waitPromise = waitForDevserverBuild ( { timeout : 5 * WATCH_DELAY } , mockContext ) ;
148+
149+ // Tick past the first debounce. The while loop will be entered.
150+ jasmine . clock ( ) . tick ( WATCH_DELAY + 1 ) ;
151+
152+ // Tick past the second debounce (inside the loop).
153+ jasmine . clock ( ) . tick ( WATCH_DELAY + 1 ) ;
154+
155+ // Now finish the build.
156+ mockProcess . stdout . emit ( 'data' , 'Application bundle generation complete.' ) ;
157+
158+ // Tick past another debounce to exit the loop.
159+ jasmine . clock ( ) . tick ( WATCH_DELAY + 1 ) ;
160+
161+ const waitResult = await waitPromise ;
162+
163+ expect ( waitResult . structuredContent . status ) . toBe ( 'success' ) ;
164+ expect ( waitResult . structuredContent . logs ) . toEqual ( [
165+ '❯ Changes detected. Rebuilding...' ,
166+ 'Application bundle generation complete.' ,
167+ ] ) ;
168+ } finally {
169+ jasmine . clock ( ) . uninstall ( ) ;
170+ }
171+ } ) ;
139172} ) ;
0 commit comments