@@ -6,33 +6,7 @@ import {
66 rejectOnAbort ,
77 throwIfAborted ,
88} from "../abort-signal"
9-
10- /**
11- * Stryker guard: fails fast if `promise` does not settle within `ms`.
12- *
13- * Stryker's per-mutant cutoff (timeoutMS 5s x timeoutFactor 1.5 ~= 7.5s) is shorter
14- * than vitest's testTimeout (20s). A mutant that removes a settle call (or an abort
15- * listener) leaves an awaited promise pending forever; without this guard the test
16- * would outlive the cutoff and the mutant would be reported as Timeout (inconclusive).
17- * Settling the guard at 500ms turns those mutants into fast failures (KILLED).
18- */
19- function withSettleGuard < T > ( promise : Promise < T > , ms = 500 ) : Promise < T > {
20- return new Promise < T > ( ( resolve , reject ) => {
21- const timer = setTimeout ( ( ) => {
22- reject ( new Error ( `settle guard timed out after ${ ms } ms` ) )
23- } , ms )
24- void promise . then (
25- ( value ) => {
26- clearTimeout ( timer )
27- resolve ( value )
28- } ,
29- ( error ) => {
30- clearTimeout ( timer )
31- reject ( error )
32- } ,
33- )
34- } )
35- }
9+ import { withSettleGuard } from "../../../../test-utils/settle-guard"
3610
3711describe ( "rejectOnAbort" , ( ) => {
3812 it ( "resolves with the pending value when it settles before the signal aborts" , async ( ) => {
@@ -64,6 +38,7 @@ describe("rejectOnAbort", () => {
6438
6539 await expect ( withSettleGuard ( rejectOnAbort ( pending , controller . signal , "TestProvider" ) ) ) . rejects . toMatchObject ( {
6640 name : "AbortError" ,
41+ message : "The TestProvider request was aborted" ,
6742 } )
6843 } )
6944
@@ -78,26 +53,38 @@ describe("rejectOnAbort", () => {
7853
7954 it ( "detaches the abort listener once the pending settles" , async ( ) => {
8055 const controller = new AbortController ( )
56+ const addSpy = vi . spyOn ( controller . signal , "addEventListener" )
8157 const removeSpy = vi . spyOn ( controller . signal , "removeEventListener" )
8258
8359 await expect (
8460 withSettleGuard ( rejectOnAbort ( Promise . resolve ( "done" ) , controller . signal , "TestProvider" ) ) ,
8561 ) . resolves . toBe ( "done" )
8662
87- expect ( removeSpy ) . toHaveBeenCalledWith ( "abort" , expect . any ( Function ) )
63+ // The settle path must remove the exact listener that was registered, not just any
64+ // function: removing a different reference would leave the original abort listener
65+ // attached to the signal.
66+ const registeredListener = addSpy . mock . calls [ 0 ] ?. [ 1 ] as EventListener | undefined
67+ expect ( removeSpy ) . toHaveBeenCalledWith ( "abort" , registeredListener )
68+ addSpy . mockRestore ( )
8869 removeSpy . mockRestore ( )
8970 } )
9071
9172 it ( "detaches the abort listener when the pending rejects" , async ( ) => {
9273 const controller = new AbortController ( )
74+ const addSpy = vi . spyOn ( controller . signal , "addEventListener" )
9375 const removeSpy = vi . spyOn ( controller . signal , "removeEventListener" )
9476 const lookupError = new Error ( "lookup failed" )
9577
9678 await expect (
9779 withSettleGuard ( rejectOnAbort ( Promise . reject ( lookupError ) , controller . signal , "TestProvider" ) ) ,
9880 ) . rejects . toBe ( lookupError )
9981
100- expect ( removeSpy ) . toHaveBeenCalledWith ( "abort" , expect . any ( Function ) )
82+ // The settle path must remove the exact listener that was registered, not just any
83+ // function: removing a different reference would leave the original abort listener
84+ // attached to the signal.
85+ const registeredListener = addSpy . mock . calls [ 0 ] ?. [ 1 ] as EventListener | undefined
86+ expect ( removeSpy ) . toHaveBeenCalledWith ( "abort" , registeredListener )
87+ addSpy . mockRestore ( )
10188 removeSpy . mockRestore ( )
10289 } )
10390} )
0 commit comments