11import type { Assertion , ExpectStatic } from '@vitest/expect'
22import type { Test } from '@vitest/runner'
33import { chai } from '@vitest/expect'
4- import { getSafeTimers } from '@vitest/utils/timers'
4+ import { delay , getSafeTimers } from '@vitest/utils/timers'
55import { getWorkerState } from '../../runtime/utils'
66
77// these matchers are not supported because they don't make sense with poll
@@ -26,6 +26,25 @@ const unsupported = [
2626 // resolves
2727]
2828
29+ /**
30+ * Attaches a `cause` property to the error if missing, copies the stack trace from the source, and throws.
31+ *
32+ * @param error - The error to throw
33+ * @param source - Error to copy the stack trace from
34+ *
35+ * @throws Always throws the provided error with an amended stack trace
36+ */
37+ function throwWithCause ( error : any , source : Error ) {
38+ if ( error . cause == null ) {
39+ error . cause = new Error ( 'Matcher did not succeed in time.' )
40+ }
41+
42+ throw copyStackTrace (
43+ error ,
44+ source ,
45+ )
46+ }
47+
2948export function createExpectPoll ( expect : ExpectStatic ) : ExpectStatic [ 'poll' ] {
3049 return function poll ( fn , options = { } ) {
3150 const state = getWorkerState ( )
@@ -64,60 +83,49 @@ export function createExpectPoll(expect: ExpectStatic): ExpectStatic['poll'] {
6483
6584 return function ( this : any , ...args : any [ ] ) {
6685 const STACK_TRACE_ERROR = new Error ( 'STACK_TRACE_ERROR' )
67- const promise = ( ) => new Promise < void > ( ( resolve , reject ) => {
68- let intervalId : any
69- let timeoutId : any
70- let lastError : any
86+ const promise = async ( ) => {
7187 const { setTimeout, clearTimeout } = getSafeTimers ( )
72- const rejectWithCause = ( error : any ) => {
73- if ( error . cause == null ) {
74- error . cause = new Error ( 'Matcher did not succeed in time.' )
75- }
76- reject (
77- copyStackTrace (
78- error ,
79- STACK_TRACE_ERROR ,
80- ) ,
81- )
82- }
83- const check = async ( ) => {
84- try {
85- chai . util . flag ( assertion , '_name' , key )
86- const obj = await fn ( )
87- chai . util . flag ( assertion , 'object' , obj )
88+
89+ let executionPhase : 'fn' | 'assertion' = 'fn'
90+ let hasTimedOut = false
91+
92+ const timerId = setTimeout ( ( ) => {
93+ hasTimedOut = true
94+ } , timeout )
95+
96+ chai . util . flag ( assertion , '_name' , key )
97+
98+ try {
99+ while ( true ) {
100+ const isLastAttempt = hasTimedOut
101+
102+ if ( isLastAttempt ) {
103+ chai . util . flag ( assertion , '_isLastPollAttempt' , true )
104+ }
105+
88106 try {
89- resolve ( await assertionFunction . call ( assertion , ...args ) )
107+ executionPhase = 'fn'
108+ const obj = await fn ( )
109+ chai . util . flag ( assertion , 'object' , obj )
110+
111+ executionPhase = 'assertion'
112+ const output = await assertionFunction . call ( assertion , ...args )
113+
114+ return output
90115 }
91116 catch ( err ) {
92- if ( chai . util . flag ( assertion , '_poll.assert_once' ) ) {
93- clearTimeout ( intervalId )
94- clearTimeout ( timeoutId )
95-
96- rejectWithCause ( err )
97- }
98- else {
99- throw err
117+ if ( isLastAttempt || ( executionPhase === 'assertion' && chai . util . flag ( assertion , '_poll.assert_once' ) ) ) {
118+ throwWithCause ( err , STACK_TRACE_ERROR )
100119 }
101- }
102- clearTimeout ( intervalId )
103- clearTimeout ( timeoutId )
104- }
105- catch ( err ) {
106- lastError = err
107- if ( ! chai . util . flag ( assertion , '_isLastPollAttempt' ) ) {
108- intervalId = setTimeout ( check , interval )
120+
121+ await delay ( interval , setTimeout )
109122 }
110123 }
111124 }
112- timeoutId = setTimeout ( ( ) => {
113- clearTimeout ( intervalId )
114- chai . util . flag ( assertion , '_isLastPollAttempt' , true )
115- check ( )
116- . then ( ( ) => rejectWithCause ( lastError ) )
117- . catch ( e => rejectWithCause ( e ) )
118- } , timeout )
119- check ( )
120- } )
125+ finally {
126+ clearTimeout ( timerId )
127+ }
128+ }
121129 let awaited = false
122130 test . onFinished ??= [ ]
123131 test . onFinished . push ( ( ) => {
0 commit comments