11import { describe , expect , it } from "bun:test"
2- import { CancellationError , Panic , TimeoutError , UnhandledException } from "../errors"
2+ import { CancellationError , Panic , TimeoutError } from "../errors"
33import { driveGen } from "../gen"
44
55class UserNotFound extends Error { }
@@ -62,68 +62,119 @@ describe("driveGen", () => {
6262 expect ( didRunAfterError ) . toBe ( false )
6363 } )
6464
65- it ( "propagates rejected yielded promise" , async ( ) => {
66- const result = await driveGen ( function * ( use ) {
67- const value = yield * use ( Promise . reject < unknown > ( new Error ( "boom" ) ) )
68- return value
69- } )
70-
71- expect ( result ) . toBeInstanceOf ( UnhandledException )
72- expect ( ( result as UnhandledException ) . cause ) . toBeInstanceOf ( Error )
73- expect ( ( ( result as UnhandledException ) . cause as Error ) . message ) . toBe ( "boom" )
65+ it ( "rejects with the original reason when a yielded promise rejects" , async ( ) => {
66+ try {
67+ await driveGen ( function * ( use ) {
68+ const value = yield * use ( Promise . reject < unknown > ( new Error ( "boom" ) ) )
69+ return value
70+ } )
71+ expect . unreachable ( "should have thrown" )
72+ } catch ( error ) {
73+ expect ( error ) . toBeInstanceOf ( Error )
74+ expect ( ( error as Error ) . message ) . toBe ( "boom" )
75+ }
7476 } )
7577
7678 it ( "preserves TimeoutError from a rejected yielded promise" , async ( ) => {
7779 const timeout = new TimeoutError ( "timed out" )
7880
79- const result = await driveGen ( function * ( use ) {
80- const value = yield * use ( Promise . reject < unknown > ( timeout ) )
81- return value
82- } )
81+ try {
82+ await driveGen ( function * ( use ) {
83+ const value = yield * use ( Promise . reject < unknown > ( timeout ) )
84+ return value
85+ } )
86+ expect . unreachable ( "should have thrown" )
87+ } catch ( error ) {
88+ expect ( error ) . toBe ( timeout )
89+ }
90+ } )
8391
84- expect ( result ) . toBe ( timeout )
92+ it ( "runs finally blocks when the first yielded promise rejects" , async ( ) => {
93+ let finalized = false
94+
95+ try {
96+ await driveGen ( function * ( use ) {
97+ try {
98+ yield * use ( Promise . reject < unknown > ( new Error ( "boom" ) ) )
99+ } finally {
100+ finalized = true
101+ }
102+ } )
103+ expect . unreachable ( "should have thrown" )
104+ } catch ( error ) {
105+ expect ( error ) . toBeInstanceOf ( Error )
106+ expect ( ( error as Error ) . message ) . toBe ( "boom" )
107+ }
108+
109+ expect ( finalized ) . toBe ( true )
85110 } )
86111
87- it ( "returns error when factory throws" , ( ) => {
88- const result = driveGen ( ( ) => {
89- throw new Error ( "factory failed" )
112+ it ( "lets the generator catch a rejected yielded promise and recover" , async ( ) => {
113+ const result = await driveGen ( function * ( use ) {
114+ try {
115+ yield * use ( Promise . reject < unknown > ( new Error ( "boom" ) ) )
116+ } catch ( error ) {
117+ expect ( error ) . toBeInstanceOf ( Error )
118+ expect ( ( error as Error ) . message ) . toBe ( "boom" )
119+ return 42
120+ }
121+
122+ return 0
90123 } )
91124
92- expect ( result ) . toBeInstanceOf ( UnhandledException )
93- expect ( ( result as UnhandledException ) . cause ) . toBeInstanceOf ( Error )
94- expect ( ( ( result as UnhandledException ) . cause as Error ) . message ) . toBe ( "factory failed" )
125+ expect ( result ) . toBe ( 42 )
126+ } )
127+
128+ it ( "throws the original error when factory throws" , ( ) => {
129+ try {
130+ driveGen ( ( ) => {
131+ throw new Error ( "factory failed" )
132+ } )
133+ expect . unreachable ( "should have thrown" )
134+ } catch ( error ) {
135+ expect ( error ) . toBeInstanceOf ( Error )
136+ expect ( ( error as Error ) . message ) . toBe ( "factory failed" )
137+ }
95138 } )
96139
97140 it ( "preserves Panic when factory throws a control error" , ( ) => {
98141 const panic = new Panic ( "FLOW_NO_EXIT" )
99142
100- const result = driveGen ( ( ) => {
101- throw panic
102- } )
103-
104- expect ( result ) . toBe ( panic )
143+ try {
144+ driveGen ( ( ) => {
145+ throw panic
146+ } )
147+ expect . unreachable ( "should have thrown" )
148+ } catch ( error ) {
149+ expect ( error ) . toBe ( panic )
150+ }
105151 } )
106152
107- it ( "returns error when generator body throws after yield" , ( ) => {
108- const result = driveGen ( function * ( use ) {
109- void ( yield * use ( 1 ) )
110- throw new Error ( "generator failed" )
111- } )
112-
113- expect ( result ) . toBeInstanceOf ( UnhandledException )
114- expect ( ( result as UnhandledException ) . cause ) . toBeInstanceOf ( Error )
115- expect ( ( ( result as UnhandledException ) . cause as Error ) . message ) . toBe ( "generator failed" )
153+ it ( "throws the original error when generator body throws after yield" , ( ) => {
154+ try {
155+ driveGen ( function * ( use ) {
156+ void ( yield * use ( 1 ) )
157+ throw new Error ( "generator failed" )
158+ } )
159+ expect . unreachable ( "should have thrown" )
160+ } catch ( error ) {
161+ expect ( error ) . toBeInstanceOf ( Error )
162+ expect ( ( error as Error ) . message ) . toBe ( "generator failed" )
163+ }
116164 } )
117165
118166 it ( "preserves Panic when generator body throws after a sync yield" , ( ) => {
119167 const panic = new Panic ( "FLOW_NO_EXIT" )
120168
121- const result = driveGen < number , number | Panic > ( function * ( use ) {
122- void ( yield * use ( 1 ) )
123- throw panic
124- } )
125-
126- expect ( result ) . toBe ( panic )
169+ try {
170+ driveGen < number , number | Panic > ( function * ( use ) {
171+ void ( yield * use ( 1 ) )
172+ throw panic
173+ } )
174+ expect . unreachable ( "should have thrown" )
175+ } catch ( error ) {
176+ expect ( error ) . toBe ( panic )
177+ }
127178 } )
128179
129180 it ( "returns explicit error values without throwing" , ( ) => {
@@ -149,68 +200,105 @@ describe("driveGen", () => {
149200 expect ( resolved . message ) . toBe ( "async return" )
150201 } )
151202
152- it ( "wraps non-Error thrown value in UnhandledException" , ( ) => {
153- const result = driveGen ( ( ) => {
154- throw "string error"
155- } )
156-
157- expect ( result ) . toBeInstanceOf ( UnhandledException )
158- expect ( ( result as UnhandledException ) . cause ) . toBe ( "string error" )
203+ it ( "throws raw non-Error values without wrapping" , ( ) => {
204+ try {
205+ driveGen ( ( ) => {
206+ throw "string error"
207+ } )
208+ expect . unreachable ( "should have thrown" )
209+ } catch ( error ) {
210+ expect ( error ) . toBe ( "string error" )
211+ }
159212 } )
160213
161- it ( "wraps thrown error in UnhandledException in async path when generator throws after yield" , async ( ) => {
162- const result = await driveGen ( function * ( use ) {
163- void ( yield * use ( Promise . resolve ( 1 ) ) )
164- throw new Error ( "async throw" )
165- } )
166-
167- expect ( result ) . toBeInstanceOf ( UnhandledException )
168- expect ( ( result as UnhandledException ) . cause ) . toBeInstanceOf ( Error )
214+ it ( "rejects with the original error when the generator throws after entering async path" , async ( ) => {
215+ try {
216+ await driveGen ( function * ( use ) {
217+ void ( yield * use ( Promise . resolve ( 1 ) ) )
218+ throw new Error ( "async throw" )
219+ } )
220+ expect . unreachable ( "should have thrown" )
221+ } catch ( error ) {
222+ expect ( error ) . toBeInstanceOf ( Error )
223+ expect ( ( error as Error ) . message ) . toBe ( "async throw" )
224+ }
169225 } )
170226
171227 it ( "preserves CancellationError when generator throws after entering async path" , async ( ) => {
172228 const cancellation = new CancellationError ( "cancelled" )
173229
174- const result = await driveGen < Promise < number > , Promise < number | CancellationError > > (
175- function * ( use ) {
230+ try {
231+ await driveGen < Promise < number > , Promise < number | CancellationError > > ( function * ( use ) {
176232 void ( yield * use ( Promise . resolve ( 1 ) ) )
177233 throw cancellation
178- }
179- )
180-
181- expect ( result ) . toBe ( cancellation )
234+ } )
235+ expect . unreachable ( "should have thrown" )
236+ } catch ( error ) {
237+ expect ( error ) . toBe ( cancellation )
238+ }
182239 } )
183240
184- it ( "wraps rejection of final returned promise in async path" , async ( ) => {
185- const result = await driveGen ( function * ( use ) {
186- void ( yield * use ( Promise . resolve ( 1 ) ) )
187- return Promise . reject ( new Error ( "final reject" ) )
188- } )
189-
190- expect ( result ) . toBeInstanceOf ( UnhandledException )
191- expect ( ( result as UnhandledException ) . cause ) . toBeInstanceOf ( Error )
241+ it ( "rejects with the original error when the final returned promise rejects" , async ( ) => {
242+ try {
243+ await driveGen ( function * ( use ) {
244+ void ( yield * use ( Promise . resolve ( 1 ) ) )
245+ return Promise . reject ( new Error ( "final reject" ) )
246+ } )
247+ expect . unreachable ( "should have thrown" )
248+ } catch ( error ) {
249+ expect ( error ) . toBeInstanceOf ( Error )
250+ expect ( ( error as Error ) . message ) . toBe ( "final reject" )
251+ }
192252 } )
193253
194254 it ( "preserves TimeoutError from a rejected final returned promise in async path" , async ( ) => {
195255 const timeout = new TimeoutError ( "timed out" )
196256
197- const result = await driveGen < Promise < number > , Promise < number | TimeoutError > > ( function * ( use ) {
198- void ( yield * use ( Promise . resolve ( 1 ) ) )
199- return Promise . reject ( timeout )
200- } )
257+ try {
258+ await driveGen < Promise < number > , Promise < number | TimeoutError > > ( function * ( use ) {
259+ void ( yield * use ( Promise . resolve ( 1 ) ) )
260+ return Promise . reject ( timeout )
261+ } )
262+ expect . unreachable ( "should have thrown" )
263+ } catch ( error ) {
264+ expect ( error ) . toBe ( timeout )
265+ }
266+ } )
201267
202- expect ( result ) . toBe ( timeout )
268+ it ( "rejects with the original reason when a later async yield rejects" , async ( ) => {
269+ try {
270+ await driveGen ( function * ( use ) {
271+ void ( yield * use ( Promise . resolve ( 1 ) ) )
272+ const value = yield * use ( Promise . reject < unknown > ( new Error ( "second reject" ) ) )
273+ return value
274+ } )
275+ expect . unreachable ( "should have thrown" )
276+ } catch ( error ) {
277+ expect ( error ) . toBeInstanceOf ( Error )
278+ expect ( ( error as Error ) . message ) . toBe ( "second reject" )
279+ }
203280 } )
204281
205- it ( "wraps rejection of second async yield" , async ( ) => {
206- const result = await driveGen ( function * ( use ) {
207- void ( yield * use ( Promise . resolve ( 1 ) ) )
208- const value = yield * use ( Promise . reject < unknown > ( new Error ( "second reject" ) ) )
209- return value
210- } )
282+ it ( "runs finally blocks when a later async yield rejects " , async ( ) => {
283+ let finalized = false
284+
285+ try {
286+ await driveGen ( function * ( use ) {
287+ void ( yield * use ( Promise . resolve ( 1 ) ) )
211288
212- expect ( result ) . toBeInstanceOf ( UnhandledException )
213- expect ( ( result as UnhandledException ) . cause ) . toBeInstanceOf ( Error )
289+ try {
290+ yield * use ( Promise . reject < unknown > ( new Error ( "second reject" ) ) )
291+ } finally {
292+ finalized = true
293+ }
294+ } )
295+ expect . unreachable ( "should have thrown" )
296+ } catch ( error ) {
297+ expect ( error ) . toBeInstanceOf ( Error )
298+ expect ( ( error as Error ) . message ) . toBe ( "second reject" )
299+ }
300+
301+ expect ( finalized ) . toBe ( true )
214302 } )
215303
216304 it ( "handles sync yield after entering async path" , async ( ) => {
0 commit comments