fix!: fail expect.poll when function didn't resolve in time (#10233) · vitest-dev/vitest@4df048c (original) (raw)

`@@ -133,40 +133,57 @@ export function createExpectPoll(expect: ExpectStatic): ExpectStatic['poll'] {

`

133

133

`}

`

134

134

``

135

135

`const { setTimeout, clearTimeout } = getSafeTimers()

`

136

``

-

137

``

`-

let executionPhase: 'fn' | 'assertion' = 'fn'

`

138

``

`-

let hasTimedOut = false

`

139

``

-

140

``

`-

const timerId = setTimeout(() => {

`

141

``

`-

hasTimedOut = true

`

142

``

`-

}, timeout)

`

``

136

`+

let timerId: ReturnType | undefined

`

``

137

`+

const timeoutController = new AbortController()

`

``

138

`+

const timeoutPromise = new Promise((resolve) => {

`

``

139

`+

timerId = setTimeout(() => {

`

``

140

`+

timeoutController.abort()

`

``

141

`+

resolve()

`

``

142

`+

}, timeout)

`

``

143

`+

})

`

``

144

`+

let lastError: unknown

`

143

145

``

144

146

`try {

`

145

147

`while (true) {

`

146

``

`-

const isLastAttempt = hasTimedOut

`

147

``

-

148

``

`-

if (isLastAttempt) {

`

149

``

`-

chai.util.flag(assertion, '_isLastPollAttempt', true)

`

150

``

`-

}

`

151

``

-

152

148

`try {

`

153

``

`-

executionPhase = 'fn'

`

154

``

`-

const obj = await fn()

`

``

149

`+

const fnResult = await raceWith(

`

``

150

`+

Promise.resolve().then(() => fn({ signal: timeoutController.signal })),

`

``

151

`+

timeoutPromise,

`

``

152

`+

)

`

``

153

`+

if (!fnResult.ok) {

`

``

154

`` +

lastError ??= new Error(expect.poll() function didn't resolve in time.)

``

``

155

`+

break

`

``

156

`+

}

`

``

157

`+

const obj = fnResult.value

`

155

158

`chai.util.flag(assertion, 'object', obj)

`

156

159

``

157

``

`-

executionPhase = 'assertion'

`

158

``

`-

const output = await assertionFunction.call(assertion, ...args)

`

``

160

`+

const assertionResult = await raceWith(

`

``

161

`+

Promise.resolve().then(() => assertionFunction.apply(assertion, args)),

`

``

162

`+

timeoutPromise,

`

``

163

`+

)

`

``

164

`+

if (!assertionResult.ok) {

`

``

165

`` +

lastError ??= new Error(expect.poll() assertion didn't resolve in time.)

``

``

166

`+

break

`

``

167

`+

}

`

``

168

`+

const output = assertionResult.value

`

159

169

`await onSettled?.({ assertion, status: 'pass' })

`

160

170

``

161

171

`return output

`

162

172

`}

`

163

173

`catch (err) {

`

164

``

`-

if (isLastAttempt || (executionPhase === 'assertion' && chai.util.flag(assertion, '_poll.assert_once'))) {

`

165

``

`-

await onSettled?.({ assertion, status: 'fail' })

`

166

``

`-

throwWithCause(err, STACK_TRACE_ERROR)

`

``

174

`+

lastError = err

`

``

175

`+

// no retry for toMatchScreenshot since

`

``

176

`+

// it owns retry/stability after the first element resolution

`

``

177

`+

if (key === 'toMatchScreenshot') {

`

``

178

`+

break

`

``

179

`+

}

`

``

180

`+

const result = await raceWith(

`

``

181

`+

delay(interval, setTimeout),

`

``

182

`+

timeoutPromise,

`

``

183

`+

)

`

``

184

`+

if (!result.ok) {

`

``

185

`+

break

`

167

186

`}

`

168

``

-

169

``

`-

await delay(interval, setTimeout)

`

170

187

`if (vi.isFakeTimers()) {

`

171

188

`vi.advanceTimersByTime(interval)

`

172

189

`}

`

`@@ -176,6 +193,10 @@ export function createExpectPoll(expect: ExpectStatic): ExpectStatic['poll'] {

`

176

193

`finally {

`

177

194

`clearTimeout(timerId)

`

178

195

`}

`

``

196

`+

if (lastError) {

`

``

197

`+

await onSettled?.({ assertion, status: 'fail' })

`

``

198

`+

throwWithCause(lastError, STACK_TRACE_ERROR)

`

``

199

`+

}

`

179

200

`}

`

180

201

`let awaited = false

`

181

202

`test.onFinished ??= []

`

`@@ -221,3 +242,17 @@ function copyStackTrace(target: Error, source: Error) {

`

221

242

`}

`

222

243

`return target

`

223

244

`}

`

``

245

+

``

246

`+

function raceWith<A, B>(

`

``

247

`+

promise: Promise,

`

``

248

`+

other?: Promise,

`

``

249

`+

): Promise<{ ok: true; value: A } | { ok: false; value: B }> {

`

``

250

`+

const left = promise.then(value => ({ ok: true as const, value }))

`

``

251

`+

if (!other) {

`

``

252

`+

return left

`

``

253

`+

}

`

``

254

`+

return Promise.race([

`

``

255

`+

left,

`

``

256

`+

other.then(value => ({ ok: false as const, value })),

`

``

257

`+

])

`

``

258

`+

}

`