@@ -216,4 +216,80 @@ suite('processStreamWithIdentifiers', () => {
216216 expect ( fragmentsProcessed . join ( '' ) ) . to . deep . equal ( inputFragments . join ( '' ) ) ;
217217 expect ( identifiersStreamed ) . to . deep . equal ( [ '\ncode1\n' , '\ncode2\n' ] ) ;
218218 } ) ;
219+
220+ test ( 'one fragment containing multiple code blocks emits event in correct order' , async ( ) => {
221+ // In case we have one fragment containing multiple code blocks, we want to make sure that
222+ // fragment notifications and identifier notifications arrive in the right order so that we're
223+ // adding code actions after the correct subfragment.
224+ // For example:
225+ // 'Text before code.\n```js\ncode1\n```\nText between code.\n```js\ncode2\n```\nText after code.'
226+ //
227+ // should emit:
228+ //
229+ // processStreamFragment: 'Text before code.\n```js\ncode1\n```'
230+ // onStreamIdentifier: '\ncode1\n'
231+ // processStreamFragment: '\nText between code.\n```js\ncode2\n```'
232+ // onStreamIdentifier: '\ncode2\n'
233+ // processStreamFragment: '\nText after code.'
234+ //
235+ // in that order to ensure we add each code action immediately after the code block
236+ // rather than add both at the end.
237+
238+ const inputFragments = [
239+ 'Text before code.\n```js\ncode1\n```\nText between code.\n```js\ncode2\n```\nText after code.' ,
240+ ] ;
241+
242+ const inputIterable = asyncIterableFromArray < string > ( inputFragments ) ;
243+ const identifier = { start : '```js' , end : '```' } ;
244+
245+ const fragmentsEmitted : {
246+ source : 'processStreamFragment' | 'onStreamIdentifier' ;
247+ content : string ;
248+ } [ ] = [ ] ;
249+
250+ const getFragmentHandler = (
251+ source : 'processStreamFragment' | 'onStreamIdentifier'
252+ ) : ( ( fragment : string ) => void ) => {
253+ return ( fragment : string ) : void => {
254+ // It's an implementation detail, but the way the code is structured today, we're splitting the emitted fragments
255+ // whenever we find either a start or end identifier. This is irrelevant as long as we're emitting the entirety of
256+ // the text until the end of the code block in `processStreamFragment` and then the code block itself in `onStreamIdentifier`.
257+ // With the code below, we're combining all subfragments with the same source to make the test verify the desired
258+ // behavior rather than the actual implementation.
259+ const lastFragment = fragmentsEmitted [ fragmentsEmitted . length - 1 ] ;
260+ if ( lastFragment ?. source === source ) {
261+ lastFragment . content += fragment ;
262+ } else {
263+ fragmentsEmitted . push ( { source, content : fragment } ) ;
264+ }
265+ } ;
266+ } ;
267+
268+ await processStreamWithIdentifiers ( {
269+ processStreamFragment : getFragmentHandler ( 'processStreamFragment' ) ,
270+ onStreamIdentifier : getFragmentHandler ( 'onStreamIdentifier' ) ,
271+ inputIterable,
272+ identifier,
273+ } ) ;
274+
275+ expect ( fragmentsEmitted ) . to . have . length ( 5 ) ;
276+ expect ( fragmentsEmitted [ 0 ] . source ) . to . equal ( 'processStreamFragment' ) ;
277+ expect ( fragmentsEmitted [ 0 ] . content ) . to . equal (
278+ 'Text before code.\n```js\ncode1\n```'
279+ ) ;
280+
281+ expect ( fragmentsEmitted [ 1 ] . source ) . to . equal ( 'onStreamIdentifier' ) ;
282+ expect ( fragmentsEmitted [ 1 ] . content ) . to . equal ( '\ncode1\n' ) ;
283+
284+ expect ( fragmentsEmitted [ 2 ] . source ) . to . equal ( 'processStreamFragment' ) ;
285+ expect ( fragmentsEmitted [ 2 ] . content ) . to . equal (
286+ '\nText between code.\n```js\ncode2\n```'
287+ ) ;
288+
289+ expect ( fragmentsEmitted [ 3 ] . source ) . to . equal ( 'onStreamIdentifier' ) ;
290+ expect ( fragmentsEmitted [ 3 ] . content ) . to . equal ( '\ncode2\n' ) ;
291+
292+ expect ( fragmentsEmitted [ 4 ] . source ) . to . equal ( 'processStreamFragment' ) ;
293+ expect ( fragmentsEmitted [ 4 ] . content ) . to . equal ( '\nText after code.' ) ;
294+ } ) ;
219295} ) ;
0 commit comments