timers-promises.spec.ts 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. /**
  2. * `node:timers/promises` over the worker's timer globals.
  3. *
  4. * The abort paths are the substance. Harness code hands these waits a
  5. * cancellation signal, and an already-aborted signal emits no further `abort`
  6. * event — so a wait that only subscribes runs its full delay before answering,
  7. * which is a cancelled operation that still costs its timeout. The delays below
  8. * are a minute long on purpose: any case that waited would fail by timing out
  9. * rather than pass slowly.
  10. */
  11. import { afterEach, describe, expect, it, vi } from 'vitest'
  12. import { scheduler, setImmediate, setTimeout } from '../../src/node/builtin_modules/implemented/timers/promises.ts'
  13. afterEach(() => { vi.restoreAllMocks() })
  14. /** The rejection reason, however the wait failed. */
  15. const rejectionOf = async (pending: Promise<unknown>): Promise<unknown> =>
  16. await pending.then(() => undefined, (error: unknown) => error)
  17. describe('setTimeout', () => {
  18. it('resolves with the value after the delay, and undefined without one', async () => {
  19. await expect(setTimeout(1, 'done')).resolves.toBe('done')
  20. await expect(setTimeout(1)).resolves.toBeUndefined()
  21. })
  22. it('rejects an already-aborted signal without arming a timer', async () => {
  23. const armed = vi.spyOn(globalThis, 'setTimeout')
  24. const pending = setTimeout(60_000, 'never', { signal: AbortSignal.abort() })
  25. // Asserted before any await, so nothing else could have armed a timer here.
  26. expect(armed).not.toHaveBeenCalled()
  27. expect(await rejectionOf(pending)).toMatchObject({ name: 'AbortError' })
  28. })
  29. it('rejects and releases the timer when the signal aborts while pending', async () => {
  30. const cleared = vi.spyOn(globalThis, 'clearTimeout')
  31. const controller = new AbortController()
  32. const pending = setTimeout(60_000, 'never', { signal: controller.signal })
  33. controller.abort()
  34. expect(await rejectionOf(pending)).toMatchObject({ name: 'AbortError' })
  35. expect(cleared).toHaveBeenCalled()
  36. })
  37. })
  38. describe('the rest of the face', () => {
  39. it('resolves setImmediate on a later macrotask with its value', async () => {
  40. await expect(setImmediate('now')).resolves.toBe('now')
  41. })
  42. it('settles both scheduler helpers and forwards the signal through wait', async () => {
  43. await expect(scheduler.wait(1)).resolves.toBeUndefined()
  44. await expect(scheduler.yield()).resolves.toBeUndefined()
  45. const aborted = scheduler.wait(60_000, { signal: AbortSignal.abort() })
  46. expect(await rejectionOf(aborted)).toMatchObject({ name: 'AbortError' })
  47. })
  48. })