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 , beforeEach } from 'vitest' ;
17
+
18
+ import { SerialRunner } from './serial_runner' ;
19
+ import { resolvablePromise } from '../promise/resolvablePromise' ;
20
+ import { exhaustMicrotasks } from '../../tests/testUtils' ;
21
+
22
+ describe ( 'SerialRunner' , ( ) => {
23
+ let serialRunner : SerialRunner ;
24
+
25
+ beforeEach ( ( ) => {
26
+ serialRunner = new SerialRunner ( ) ;
27
+ } ) ;
28
+
29
+ it ( 'should return result from a single async function' , async ( ) => {
30
+ const fn = ( ) => Promise . resolve ( 'result' ) ;
31
+
32
+ const result = await serialRunner . run ( fn ) ;
33
+
34
+ expect ( result ) . toBe ( 'result' ) ;
35
+ } ) ;
36
+
37
+ it ( 'should reject with same error when the passed function rejects' , async ( ) => {
38
+ const error = new Error ( 'test error' ) ;
39
+ const fn = ( ) => Promise . reject ( error ) ;
40
+
41
+ await expect ( serialRunner . run ( fn ) ) . rejects . toThrow ( error ) ;
42
+ } ) ;
43
+
44
+ it ( 'should execute multiple async functions in order' , async ( ) => {
45
+ const executionOrder : number [ ] = [ ] ;
46
+ const promises = [ resolvablePromise ( ) , resolvablePromise ( ) , resolvablePromise ( ) ] ;
47
+
48
+ const createTask = ( id : number ) => async ( ) => {
49
+ executionOrder . push ( id ) ;
50
+ await promises [ id ] ;
51
+ return id ;
52
+ } ;
53
+
54
+ const results = [ serialRunner . run ( createTask ( 0 ) ) , serialRunner . run ( createTask ( 1 ) ) , serialRunner . run ( createTask ( 2 ) ) ] ;
55
+
56
+ // only first task should have started
57
+ await exhaustMicrotasks ( ) ;
58
+ expect ( executionOrder ) . toEqual ( [ 0 ] ) ;
59
+
60
+ // Resolve first task - second should start
61
+ promises [ 0 ] . resolve ( '' ) ;
62
+ await exhaustMicrotasks ( ) ;
63
+ expect ( executionOrder ) . toEqual ( [ 0 , 1 ] ) ;
64
+
65
+ // Resolve second task - third should start
66
+ promises [ 1 ] . resolve ( '' ) ;
67
+ await exhaustMicrotasks ( ) ;
68
+ expect ( executionOrder ) . toEqual ( [ 0 , 1 , 2 ] ) ;
69
+
70
+ // Resolve third task - all done
71
+ promises [ 2 ] . resolve ( '' ) ;
72
+
73
+ // Verify all results are correct
74
+ expect ( await results [ 0 ] ) . toBe ( 0 ) ;
75
+ expect ( await results [ 1 ] ) . toBe ( 1 ) ;
76
+ expect ( await results [ 2 ] ) . toBe ( 2 ) ;
77
+ } ) ;
78
+
79
+ it ( 'should continue execution even if one function throws an error' , async ( ) => {
80
+ const executionOrder : number [ ] = [ ] ;
81
+ const promises = [ resolvablePromise ( ) , resolvablePromise ( ) , resolvablePromise ( ) ] ;
82
+
83
+ const createTask = ( id : number ) => async ( ) => {
84
+ executionOrder . push ( id ) ;
85
+ await promises [ id ] ;
86
+ return id ;
87
+ } ;
88
+
89
+ const results = [ serialRunner . run ( createTask ( 0 ) ) , serialRunner . run ( createTask ( 1 ) ) , serialRunner . run ( createTask ( 2 ) ) ] ;
90
+
91
+ // only first task should have started
92
+ await exhaustMicrotasks ( ) ;
93
+ expect ( executionOrder ) . toEqual ( [ 0 ] ) ;
94
+
95
+ // reject first task - second should still start
96
+ promises [ 0 ] . reject ( new Error ( 'first error' ) ) ;
97
+ await exhaustMicrotasks ( ) ;
98
+ expect ( executionOrder ) . toEqual ( [ 0 , 1 ] ) ;
99
+
100
+ // reject second task - third should still start
101
+ promises [ 1 ] . reject ( new Error ( 'second error' ) ) ;
102
+ await exhaustMicrotasks ( ) ;
103
+ expect ( executionOrder ) . toEqual ( [ 0 , 1 , 2 ] ) ;
104
+
105
+ // Resolve third task - all done
106
+ promises [ 2 ] . resolve ( '' ) ;
107
+
108
+ // Verify results - first and third succeed, second fails
109
+ await expect ( results [ 0 ] ) . rejects . toThrow ( 'first error' ) ;
110
+ await expect ( results [ 1 ] ) . rejects . toThrow ( 'second error' ) ;
111
+ await expect ( results [ 2 ] ) . resolves . toBe ( 2 ) ;
112
+ } ) ;
113
+
114
+ it ( 'should handle functions that return different types' , async ( ) => {
115
+ const numberFn = ( ) => Promise . resolve ( 42 ) ;
116
+ const stringFn = ( ) => Promise . resolve ( 'hello' ) ;
117
+ const objectFn = ( ) => Promise . resolve ( { key : 'value' } ) ;
118
+ const arrayFn = ( ) => Promise . resolve ( [ 1 , 2 , 3 ] ) ;
119
+ const booleanFn = ( ) => Promise . resolve ( true ) ;
120
+ const nullFn = ( ) => Promise . resolve ( null ) ;
121
+ const undefinedFn = ( ) => Promise . resolve ( undefined ) ;
122
+
123
+ const results = await Promise . all ( [
124
+ serialRunner . run ( numberFn ) ,
125
+ serialRunner . run ( stringFn ) ,
126
+ serialRunner . run ( objectFn ) ,
127
+ serialRunner . run ( arrayFn ) ,
128
+ serialRunner . run ( booleanFn ) ,
129
+ serialRunner . run ( nullFn ) ,
130
+ serialRunner . run ( undefinedFn ) ,
131
+ ] ) ;
132
+
133
+ expect ( results ) . toEqual ( [ 42 , 'hello' , { key : 'value' } , [ 1 , 2 , 3 ] , true , null , undefined ] ) ;
134
+ } ) ;
135
+
136
+ it ( 'should handle empty function that returns undefined' , async ( ) => {
137
+ const emptyFn = ( ) => Promise . resolve ( undefined ) ;
138
+
139
+ const result = await serialRunner . run ( emptyFn ) ;
140
+
141
+ expect ( result ) . toBeUndefined ( ) ;
142
+ } ) ;
143
+ } ) ;
0 commit comments