1+ /**
2+ * Copyright 2025, Optimizely
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * https://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+ import { describe , it , expect , vi , beforeEach } from 'vitest' ;
17+
18+ import { SerialRunner } from './serial_runner' ;
19+ import { resolvablePromise } from '../promise/resolvablePromise' ;
20+ import { exhaustMicrotasks } from '../../tests/testUtils' ;
21+ import { Maybe } from '../type' ;
22+
23+ describe ( 'SerialRunner' , ( ) => {
24+ let serialRunner : SerialRunner ;
25+
26+ beforeEach ( ( ) => {
27+ serialRunner = new SerialRunner ( ) ;
28+ } ) ;
29+
30+ it ( 'should return result from a single async function' , async ( ) => {
31+ const fn = ( ) => Promise . resolve ( 'result' ) ;
32+
33+ const result = await serialRunner . run ( fn ) ;
34+
35+ expect ( result ) . toBe ( 'result' ) ;
36+ } ) ;
37+
38+ it ( 'should reject with same error when the passed function rejects' , async ( ) => {
39+ const error = new Error ( 'test error' ) ;
40+ const fn = ( ) => Promise . reject ( error ) ;
41+
42+ await expect ( serialRunner . run ( fn ) ) . rejects . toThrow ( error ) ;
43+ } ) ;
44+
45+ it ( 'should execute multiple async functions in order' , async ( ) => {
46+ // events to track execution order
47+ // begin_1 means call 1 started
48+ // end_1 means call 1 ended ...
49+ const events : string [ ] = [ ] ;
50+
51+ const nCall = 10 ;
52+
53+ const promises = Array . from ( { length : nCall } , ( ) => resolvablePromise ( ) ) ;
54+ const getFn = ( i : number ) => {
55+ return async ( ) : Promise < number > => {
56+ events . push ( `begin_${ i } ` ) ;
57+ await promises [ i ] ;
58+ events . push ( `end_${ i } ` ) ;
59+ return i ;
60+ }
61+ }
62+
63+ const resultPromises = [ ] ;
64+ for ( let i = 0 ; i < nCall ; i ++ ) {
65+ resultPromises . push ( serialRunner . run ( getFn ( i ) ) ) ;
66+ }
67+
68+ await exhaustMicrotasks ( ) ;
69+
70+ const expectedEvents = [ 'begin_0' ] ;
71+
72+ expect ( events ) . toEqual ( expectedEvents ) ;
73+
74+ for ( let i = 0 ; i < nCall - 1 ; i ++ ) {
75+ promises [ i ] . resolve ( '' ) ;
76+ await exhaustMicrotasks ( ) ;
77+
78+ expectedEvents . push ( `end_${ i } ` ) ;
79+ expectedEvents . push ( `begin_${ i + 1 } ` ) ;
80+
81+ expect ( events ) . toEqual ( expectedEvents ) ;
82+ }
83+
84+ promises [ nCall - 1 ] . resolve ( '' ) ;
85+ await exhaustMicrotasks ( ) ;
86+
87+ expectedEvents . push ( `end_${ nCall - 1 } ` ) ;
88+ expect ( events ) . toEqual ( expectedEvents ) ;
89+
90+ for ( let i = 0 ; i < nCall ; i ++ ) {
91+ await expect ( resultPromises [ i ] ) . resolves . toBe ( i ) ;
92+ }
93+ } ) ;
94+
95+ it ( 'should continue execution even if one function throws an error' , async ( ) => {
96+ const events : string [ ] = [ ] ;
97+
98+ const nCall = 5 ;
99+ const err = [ false , true , false , true , true ] ;
100+
101+ const promises = Array . from ( { length : nCall } , ( ) => resolvablePromise ( ) ) ;
102+
103+ const getFn = ( i : number ) => {
104+ return async ( ) : Promise < number > => {
105+ events . push ( `begin_${ i } ` ) ;
106+ let err = false ;
107+ try {
108+ await promises [ i ] ;
109+ } catch ( e ) {
110+ err = true ;
111+ }
112+
113+ events . push ( `end_${ i } ` ) ;
114+ if ( err ) {
115+ throw new Error ( `error_${ i } ` ) ;
116+ }
117+ return i ;
118+ }
119+ }
120+
121+ const resultPromises = [ ] ;
122+ for ( let i = 0 ; i < nCall ; i ++ ) {
123+ resultPromises . push ( serialRunner . run ( getFn ( i ) ) ) ;
124+ }
125+
126+ await exhaustMicrotasks ( ) ;
127+
128+ const expectedEvents = [ 'begin_0' ] ;
129+
130+ expect ( events ) . toEqual ( expectedEvents ) ;
131+
132+ const endFn = ( i : number ) => {
133+ if ( err [ i ] ) {
134+ promises [ i ] . reject ( new Error ( 'error' ) ) ;
135+ } else {
136+ promises [ i ] . resolve ( '' ) ;
137+ }
138+ }
139+
140+ for ( let i = 0 ; i < nCall - 1 ; i ++ ) {
141+ endFn ( i ) ;
142+
143+ await exhaustMicrotasks ( ) ;
144+
145+ expectedEvents . push ( `end_${ i } ` ) ;
146+ expectedEvents . push ( `begin_${ i + 1 } ` ) ;
147+
148+ expect ( events ) . toEqual ( expectedEvents ) ;
149+ }
150+
151+ endFn ( nCall - 1 ) ;
152+ await exhaustMicrotasks ( ) ;
153+
154+ expectedEvents . push ( `end_${ nCall - 1 } ` ) ;
155+ expect ( events ) . toEqual ( expectedEvents ) ;
156+
157+ for ( let i = 0 ; i < nCall ; i ++ ) {
158+ if ( err [ i ] ) {
159+ await expect ( resultPromises [ i ] ) . rejects . toThrow ( `error_${ i } ` ) ;
160+ } else {
161+ await expect ( resultPromises [ i ] ) . resolves . toBe ( i ) ;
162+ }
163+ }
164+ } ) ;
165+
166+ it ( 'should handle functions that return different types' , async ( ) => {
167+ const numberFn = ( ) => Promise . resolve ( 42 ) ;
168+ const stringFn = ( ) => Promise . resolve ( 'hello' ) ;
169+ const objectFn = ( ) => Promise . resolve ( { key : 'value' } ) ;
170+ const arrayFn = ( ) => Promise . resolve ( [ 1 , 2 , 3 ] ) ;
171+ const booleanFn = ( ) => Promise . resolve ( true ) ;
172+ const nullFn = ( ) => Promise . resolve ( null ) ;
173+ const undefinedFn = ( ) => Promise . resolve ( undefined ) ;
174+
175+ const results = await Promise . all ( [
176+ serialRunner . run ( numberFn ) ,
177+ serialRunner . run ( stringFn ) ,
178+ serialRunner . run ( objectFn ) ,
179+ serialRunner . run ( arrayFn ) ,
180+ serialRunner . run ( booleanFn ) ,
181+ serialRunner . run ( nullFn ) ,
182+ serialRunner . run ( undefinedFn ) ,
183+ ] ) ;
184+
185+ expect ( results ) . toEqual ( [ 42 , 'hello' , { key : 'value' } , [ 1 , 2 , 3 ] , true , null , undefined ] ) ;
186+ } ) ;
187+
188+ it ( 'should handle empty function that returns undefined' , async ( ) => {
189+ const emptyFn = ( ) => Promise . resolve ( undefined ) ;
190+
191+ const result = await serialRunner . run ( emptyFn ) ;
192+
193+ expect ( result ) . toBeUndefined ( ) ;
194+ } ) ;
195+ } ) ;
0 commit comments