@@ -158,6 +158,101 @@ describe("LongPoll", () => {
158158 expect . any ( Function )
159159 )
160160 } )
161+
162+ it ( "coalesces rapid send() calls and buffers sends made during an in-flight batch" , ( ) => {
163+ jest . useFakeTimers ( )
164+ try {
165+ const longpoll = new LongPoll ( "http://localhost/socket/longpoll" , undefined )
166+ longpoll . timeout = 1000
167+ // suppress the initial poll() that the constructor schedules via setTimeout(0)
168+ longpoll . poll = jest . fn ( )
169+
170+ const calls = [ ]
171+ Ajax . request . mockImplementation ( ( method , url , headers , body , timeout , ontimeout , callback ) => {
172+ calls . push ( { method, body, callback} )
173+ return { abort : jest . fn ( ) }
174+ } )
175+
176+ // Three sends in the same tick should collapse into one currentBatch
177+ longpoll . send ( "a" )
178+ longpoll . send ( "b" )
179+ longpoll . send ( "c" )
180+
181+ expect ( calls ) . toHaveLength ( 0 )
182+ expect ( longpoll . currentBatch ) . toEqual ( [ "a" , "b" , "c" ] )
183+
184+ // Flush the setTimeout(0) — currentBatch becomes one POST
185+ jest . runOnlyPendingTimers ( )
186+
187+ expect ( calls ) . toHaveLength ( 1 )
188+ expect ( calls [ 0 ] . method ) . toBe ( "POST" )
189+ expect ( calls [ 0 ] . body ) . toBe ( "a\nb\nc" )
190+ expect ( longpoll . currentBatch ) . toBeNull ( )
191+ expect ( longpoll . awaitingBatchAck ) . toBe ( true )
192+
193+ // Sends during in-flight ack go to batchBuffer, not a new request
194+ longpoll . send ( "d" )
195+ longpoll . send ( "e" )
196+ expect ( calls ) . toHaveLength ( 1 )
197+ expect ( longpoll . batchBuffer ) . toEqual ( [ "d" , "e" ] )
198+
199+ // Ack the first batch — the buffered sends should be flushed as the next POST
200+ calls [ 0 ] . callback ( { status : 200 } )
201+
202+ expect ( calls ) . toHaveLength ( 2 )
203+ expect ( calls [ 1 ] . body ) . toBe ( "d\ne" )
204+ expect ( longpoll . batchBuffer ) . toEqual ( [ ] )
205+ expect ( longpoll . awaitingBatchAck ) . toBe ( true )
206+
207+ // Ack the buffered batch — nothing left to send
208+ calls [ 1 ] . callback ( { status : 200 } )
209+ expect ( calls ) . toHaveLength ( 2 )
210+ expect ( longpoll . awaitingBatchAck ) . toBe ( false )
211+ } finally {
212+ jest . useRealTimers ( )
213+ }
214+ } )
215+
216+ it ( "splits 150 rapid send() calls into two requests in order" , ( ) => {
217+ jest . useFakeTimers ( )
218+ try {
219+ const longpoll = new LongPoll ( "http://localhost/socket/longpoll" , undefined )
220+ longpoll . timeout = 1000
221+ longpoll . poll = jest . fn ( )
222+
223+ const calls = [ ]
224+ Ajax . request . mockImplementation ( ( method , url , headers , body , timeout , ontimeout , callback ) => {
225+ calls . push ( { body, callback} )
226+ return { abort : jest . fn ( ) }
227+ } )
228+
229+ for ( let i = 0 ; i < 150 ; i ++ ) { longpoll . send ( `m${ i } ` ) }
230+
231+ // Flush the setTimeout(0) so batchSend runs on the full 150-entry batch
232+ jest . runOnlyPendingTimers ( )
233+
234+ expect ( calls ) . toHaveLength ( 1 )
235+ const firstLines = calls [ 0 ] . body . split ( "\n" )
236+ expect ( firstLines ) . toHaveLength ( 100 )
237+ expect ( firstLines [ 0 ] ) . toBe ( "m0" )
238+ expect ( firstLines [ 99 ] ) . toBe ( "m99" )
239+
240+ // Ack the first chunk — batchSend should recurse with the remaining 50
241+ calls [ 0 ] . callback ( { status : 200 } )
242+
243+ expect ( calls ) . toHaveLength ( 2 )
244+ const secondLines = calls [ 1 ] . body . split ( "\n" )
245+ expect ( secondLines ) . toHaveLength ( 50 )
246+ expect ( secondLines [ 0 ] ) . toBe ( "m100" )
247+ expect ( secondLines [ 49 ] ) . toBe ( "m149" )
248+
249+ calls [ 1 ] . callback ( { status : 200 } )
250+ expect ( calls ) . toHaveLength ( 2 )
251+ expect ( longpoll . awaitingBatchAck ) . toBe ( false )
252+ } finally {
253+ jest . useRealTimers ( )
254+ }
255+ } )
161256 } )
162257} )
163258
0 commit comments