@exodus/test 1.0.0-rc.90 → 1.0.0-rc.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,8 @@ It can run your existing tests on [all runtimes and also browsers](#engines), wi
14
14
  [v8 CLI](https://v8.dev/docs/d8), JSC, [Hermes](https://hermesengine.dev), [SpiderMonkey](https://spidermonkey.dev/),
15
15
  Chrome, Firefox, WebKit, Brave, Microsoft Edge,
16
16
  [QuickJS](https://github.com/quickjs-ng/quickjs), [XS](https://github.com/Moddable-OpenSource/moddable-xst),
17
- [GraalJS](https://github.com/oracle/graaljs) and [Escargot](https://github.com/Samsung/escargot).
17
+ [GraalJS](https://github.com/oracle/graaljs), [Escargot](https://github.com/Samsung/escargot),
18
+ and even [engine262](https://github.com/engine262/engine262).
18
19
  - Testsuite-agnostic — can run any file as long as it sets exit code based on test results
19
20
  - Built-in [Jest](https://jestjs.io) compatibility (with `--jest`), including `jest.*` global
20
21
  - Up to ~10x faster depending on the original setup
@@ -24,7 +25,6 @@ It can run your existing tests on [all runtimes and also browsers](#engines), wi
24
25
  - [test.concurrent](https://jestjs.io/docs/api#testconcurrentname-fn-timeout)
25
26
  - Module mocks, including for ESM modules (already loaded ESM modules can be mocked only on `node:test`)
26
27
  - Loads Jest configuration
27
- - It works on Hermes too!
28
28
  - Built-in network record/replay for offline tests, mocking `fetch` and `WebSocket` sessions
29
29
  - `--drop-network` support for guaranteed offline testing
30
30
  - Native code coverage via v8 (Node.js or [c8](https://github.com/bcoe/c8)), with istanbul reporters
@@ -64,15 +64,17 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
64
64
  - `firefox:puppeteer` — Firefox
65
65
  - `brave:puppeteer` — Brave
66
66
  - `msedge:puppeteer` — Microsoft Edge
67
- - Barebone engines (system-provided or installed with `npx jsvu`):
67
+ - Barebone engines (system-provided or installed with `npx jsvu` / `npx esvu`):
68
68
  - `d8:bundle` — [v8 CLI](https://v8.dev/docs/d8) (Chrome/Blink/Node.js JavaScript engine)
69
69
  - `jsc:bundle` — [JavaScriptCore](https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html) (Safari/WebKit JavaScript engine)
70
70
  - `hermes:bundle` — [Hermes](https://hermesengine.dev) (React Native JavaScript engine)
71
71
  - `spidermonkey:bundle` — [SpiderMonkey](https://spidermonkey.dev/) (Firefox/Gecko JavaScript engine)
72
- - `quickjs:bundle` — [QuickJS](https://github.com/quickjs-ng/quickjs) (note [quickjs-ng/quickjs#39](https://github.com/quickjs-ng/quickjs/issues/39) though)
72
+ - `quickjs:bundle` — [QuickJS](https://github.com/quickjs-ng/quickjs)
73
73
  - `xs:bundle` — [XS](https://github.com/Moddable-OpenSource/moddable-xst)
74
74
  - `graaljs:bundle` — [GraalJS](https://github.com/oracle/graaljs)
75
75
  - `escargot:bundle` — [Escargot](https://github.com/Samsung/escargot)
76
+ - `engine262:bundle` - [engine262](https://github.com/engine262/engine262), the per-spec implementation of ECMA-262
77
+ (install with [esvu](https://npmjs.com/package/esvu))
76
78
 
77
79
  ## Reporter samples
78
80
 
package/bin/browsers.js CHANGED
@@ -72,7 +72,7 @@ export async function run(runner, args, { binary, devtools, dropNetwork, timeout
72
72
  const [stdout, stderr] = [[], []]
73
73
 
74
74
  assert(Object.hasOwn(launchers, runner), 'Unexpected runner')
75
- if (!launched[runner]) launched[runner] = launchers[runner]({ binary, devtools })
75
+ if (!launched[runner]) launched[runner] = launchers[runner]({ binary, devtools: !!devtools })
76
76
  const { page, context } = await newPage(runner, await launched[runner], { binary, dropNetwork })
77
77
 
78
78
  if (throttle) {
@@ -6,6 +6,7 @@ import { createRequire } from 'node:module'
6
6
  const require = createRequire(import.meta.url)
7
7
  const nvm = process.env.NVM_BIN ? (x) => join(process.env.NVM_BIN, '../lib/node_modules', x) : null
8
8
  const jsvu = (x) => join(homedir(), '.jsvu/bin', x)
9
+ const esvu = (x) => join(homedir(), '.esvu/bin', x)
9
10
 
10
11
  // Can modify PATH to add the binary to it!
11
12
  function findBinaryOnce(name) {
@@ -46,18 +47,21 @@ function findBinaryOnce(name) {
46
47
  case 'jsc':
47
48
  return findFile([
48
49
  (bin) => jsvu(bin), // prefer jsvu
50
+ (bin) => esvu(bin),
49
51
  (bin) => `/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Helpers/${bin}`,
50
52
  (bin) => `/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/${bin}`,
51
53
  ])
52
54
  case 'd8':
53
- return findFile([() => jsvu('v8')]) // jsvu names it v8
55
+ return findFile([() => jsvu('v8'), () => esvu('v8')]) // jsvu/esvu name it v8
54
56
  case 'spidermonkey':
55
57
  case 'quickjs':
56
58
  case 'graaljs':
57
59
  case 'escargot':
58
- return findFile([jsvu])
60
+ case 'ladybird-js': // naming by esvu
61
+ case 'engine262':
62
+ return findFile([jsvu, esvu])
59
63
  case 'xs':
60
- return findFile([jsvu], false)
64
+ return findFile([jsvu, esvu], false)
61
65
  case 'electron':
62
66
  return require('electron')
63
67
  case 'c8':
package/bin/index.js CHANGED
@@ -40,6 +40,7 @@ const ENGINES = new Map(
40
40
  'jsc:bundle': { binary: 'jsc', target: 'safari13', ...bareboneOpts },
41
41
  'hermes:bundle': { binary: 'hermes', binaryArgs: hermesAv, target: 'es2018', ...bareboneOpts },
42
42
  'spidermonkey:bundle': { binary: 'spidermonkey', ...bareboneOpts },
43
+ 'engine262:bundle': { binary: 'engine262', ...bareboneOpts },
43
44
  'quickjs:bundle': { binary: 'quickjs', binaryArgs: ['--std'], ...bareboneOpts },
44
45
  'xs:bundle': { binary: 'xs', ...bareboneOpts },
45
46
  'graaljs:bundle': { binary: 'graaljs', ...bareboneOpts },
@@ -56,6 +57,8 @@ const ENGINES = new Map(
56
57
  'msedge:playwright': { binary: 'msedge', browsers: 'playwright', ...bundleOpts },
57
58
  })
58
59
  )
60
+ const barebonesOk = ['d8', 'spidermonkey', 'quickjs', 'xs', 'hermes']
61
+ const barebonesUnhandled = ['jsc', 'escargot', 'graaljs', 'engine262']
59
62
 
60
63
  const getEnvFlag = (name) => {
61
64
  if (!Object.hasOwn(process.env, name)) return false
@@ -185,7 +188,11 @@ function parseOptions() {
185
188
  options.flagEngine = true
186
189
  break
187
190
  case '--devtools':
188
- options.devtools = true
191
+ case '--inspect-brk':
192
+ options.devtools = '--inspect-brk'
193
+ break
194
+ case '--inspect':
195
+ if (!options.devtools) options.devtools = '--inspect'
189
196
  break
190
197
  case '--debug-files':
191
198
  options.debug.files = true
@@ -256,6 +263,7 @@ if (!process.env.FORCE_COLOR && process.stdout.hasColors?.() && process.stderr.h
256
263
  }
257
264
 
258
265
  const engineName = `${options.engine} engine` // used for warnings to user
266
+ const engineFlagError = (flag) => `${engineName} does not support --${flag}`
259
267
  const engineOptions = ENGINES.get(options.engine)
260
268
  assert(engineOptions, `Unknown engine: ${options.engine}`)
261
269
  Object.assign(options, engineOptions)
@@ -269,8 +277,8 @@ setEnv('EXODUS_TEST_IS_BROWSER', isBrowserLike ? '1' : '')
269
277
  setEnv('EXODUS_TEST_IS_BAREBONE', options.barebone ? '1' : '')
270
278
  setEnv('EXODUS_TEST_ENVIRONMENT', options.bundle ? 'bundle' : '') // perhaps switch to _IS_BUNDLED?
271
279
 
272
- assert(!options.devtools || isBrowserLike, '--devtools can be only used with browser engines')
273
- assert(!options.throttle || options.browsers, `${engineName} does not support --throttle-cpu`)
280
+ assert(!options.devtools || isBrowserLike || !options.pure, engineFlagError('devtools'))
281
+ assert(!options.throttle || options.browsers, engineFlagError('throttle-cpu'))
274
282
 
275
283
  const require = createRequire(import.meta.url)
276
284
  const resolveRequire = (query) => require.resolve(query)
@@ -577,7 +585,7 @@ async function launch(binary, args, opts = {}, buffering = false) {
577
585
  return browsers.run(runner, args, { binary, devtools, dropNetwork, timeout, throttle })
578
586
  }
579
587
 
580
- const barebones = ['d8', 'jsc', 'spidermonkey', 'quickjs', 'xs', 'escargot', 'graaljs', 'hermes']
588
+ const barebones = [...barebonesOk, ...barebonesUnhandled]
581
589
  assertBinary(binary, ['node', 'bun', 'deno', 'electron', ...barebones, 'v8']) // v8 is an alias to d8
582
590
  if (options.dropNetwork) {
583
591
  switch (process.platform) {
@@ -601,7 +609,7 @@ async function launch(binary, args, opts = {}, buffering = false) {
601
609
  if (options.pure) {
602
610
  setEnv('EXODUS_TEST_CONTEXT', 'pure')
603
611
  warnHuman(`${engineName} is experimental and may not work an expected`)
604
- const missUnhandled = ['jsc', 'escargot', 'graaljs'].includes(options.platform) || isBrowserLike
612
+ const missUnhandled = barebonesUnhandled.includes(options.platform) || isBrowserLike
605
613
  if (missUnhandled) warnHuman(`Warning: ${engineName} does not have unhandled rejections tracking`)
606
614
 
607
615
  const runOne = async (inputFile, attempt = 0) => {
@@ -690,6 +698,15 @@ if (options.pure) {
690
698
  assert(files.length > 0) // otherwise we can run recursively
691
699
  assert(!options.binaryArgs)
692
700
  if (options.concurrency) args.push('--test-concurrency', options.concurrency)
701
+ if (['--inspect', '--inspect-brk'].includes(options.devtools)) {
702
+ args.push(options.devtools)
703
+ console.warn(
704
+ options.devtools === '--inspect-brk'
705
+ ? 'Open chrome://inspect/ to connect devtools, waiting'
706
+ : 'Open chrome://inspect/ to connect devtools\nUse --inspect-brk to wait for inspector'
707
+ )
708
+ }
709
+
693
710
  const { code } = await launch(options.binary, [...args, ...files])
694
711
  process.exitCode = code
695
712
  }
package/bundler/bundle.js CHANGED
@@ -38,6 +38,17 @@ const loadPipeline = [
38
38
  .replace(/\b(__dirname|import\.meta\.dirname)\b/g, JSON.stringify(dirname(filepath)))
39
39
  .replace(/\b(__filename|import\.meta\.filename)\b/g, JSON.stringify(filepath))
40
40
 
41
+ if (filepath.endsWith('/node_modules/chalk/source/templates.js')) {
42
+ // It has an invalid regex on which engine262 fails
43
+ res = res.replace(
44
+ 'const ESCAPE_REGEX = /\\\\(u(?:[a-f\\d]{4}|{[a-f\\d]{1,6}})|x[a-f\\d]{2}|.)|([^\\\\])/gi;',
45
+ 'const ESCAPE_REGEX = /\\\\(u(?:[a-f\\d]{4}|\\{[a-f\\d]{1,6}\\})|x[a-f\\d]{2}|.)|([^\\\\])/giu;'
46
+ )
47
+ } else if (filepath.endsWith('/node_modules/qs/lib/parse.js')) {
48
+ res = res.replace('var brackets = /(\\[[^[\\]]*])/;', 'var brackets = /(\\[[^[\\]]*\\])/;')
49
+ res = res.replace('var child = /(\\[[^[\\]]*])/g;', 'var child = /(\\[[^[\\]]*\\])/g;')
50
+ }
51
+
41
52
  // Unneded polyfills
42
53
  for (const [a, b] of Object.entries({
43
54
  'is-nan': 'Number.isNaN', // https://www.npmjs.com/package/is-nan description: ES2015-compliant shim for Number.isNaN
@@ -6,6 +6,7 @@ if (!globalThis.Buffer) globalThis.Buffer = require('buffer').Buffer
6
6
 
7
7
  const consoleKeys = ['log', 'error', 'warn', 'info', 'debug', 'trace']
8
8
  const { print } = globalThis
9
+ if (process.env.EXODUS_TEST_PLATFORM === 'engine262') delete globalThis.console // prints [object Object] on everything
9
10
  if (!globalThis.console) globalThis.console = Object.fromEntries(consoleKeys.map((k) => [k, print])) // eslint-disable-line no-undef
10
11
  for (const k of consoleKeys) if (!console[k]) console[k] = console.log // SpiderMonkey has console but no console.error
11
12
 
@@ -139,7 +140,8 @@ if (
139
140
 
140
141
  // TODO: use interrupt timers on jsc
141
142
 
142
- const schedule = setTimeoutOriginal || ((x) => tickTimes(50).then(() => x())) // e.g. SpiderMonkey doesn't even have setTimeout
143
+ const tickPromiseInterval = process.env.EXODUS_TEST_PLATFORM === 'engine262' ? 5 : 50 // engine262 is slow
144
+ const schedule = setTimeoutOriginal || ((x) => tickTimes(tickPromiseInterval).then(() => x())) // e.g. SpiderMonkey doesn't even have setTimeout
143
145
  const dateNow = Date.now.bind(Date)
144
146
  const precision = clearTimeoutOriginal ? Infinity : 10 // have to tick this fast for clearTimeout to work
145
147
  let current = 0
@@ -197,7 +199,7 @@ if (
197
199
  const queueTick = () => {
198
200
  current++ // safeguard
199
201
  while (queueMicrotick() !== null);
200
- if (queue.length > 1) restartLoop()
202
+ if (queue.length > 0) restartLoop()
201
203
  }
202
204
 
203
205
  globalThis.setTimeout = (callback, delay, ...args) =>
@@ -27,6 +27,6 @@ function fileURLToPath(url, options) {
27
27
 
28
28
  module.exports = { ...urlLib, pathToFileURL, fileURLToPath }
29
29
 
30
- const defineExport = (k, get) => Object.defineProperty(module.exports, k, { get, enumerable: true })
31
- defineExport('URL', () => globalThis.URL)
32
- defineExport('URLSearchParams', () => globalThis.URLSearchParams)
30
+ for (const name of ['URL', 'URLSearchParams']) {
31
+ Object.defineProperty(module.exports, name, { get: () => globalThis[name], enumerable: true })
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/test",
3
- "version": "1.0.0-rc.90",
3
+ "version": "1.0.0-rc.92",
4
4
  "author": "Exodus Movement, Inc.",
5
5
  "description": "A test suite runner",
6
6
  "homepage": "https://github.com/ExodusMovement/test",
@@ -134,6 +134,7 @@
134
134
  "test:jsc": "EXODUS_TEST_ENGINE=jsc:bundle npm run test:_bundle --",
135
135
  "test:hermes": "EXODUS_TEST_ENGINE=hermes:bundle npm run test:_bundle --",
136
136
  "test:spidermonkey": "EXODUS_TEST_ENGINE=spidermonkey:bundle npm run test:_bundle --",
137
+ "test:engine262": "EXODUS_TEST_ENGINE=engine262:bundle npm run test:_bundle --",
137
138
  "test:quickjs": "EXODUS_TEST_ENGINE=quickjs:bundle npm run test:_bundle --",
138
139
  "test:xs": "EXODUS_TEST_ENGINE=xs:bundle npm run test:_bundle --",
139
140
  "test:graaljs": "EXODUS_TEST_ENGINE=graaljs:bundle npm run test:_bundle --",
@@ -157,7 +158,7 @@
157
158
  "@babel/plugin-transform-private-methods": "^7.0.0",
158
159
  "@babel/register": "^7.0.0",
159
160
  "@chalker/queue": "^1.0.1",
160
- "@exodus/replay": "^1.0.0-rc.8",
161
+ "@exodus/replay": "^1.0.0-rc.9",
161
162
  "@ungap/url-search-params": "^0.2.2",
162
163
  "amaro": "^0.0.5",
163
164
  "assert": "^2.1.0",
@@ -133,6 +133,12 @@ async function runContext(context) {
133
133
  const guard = { id: null, failed: false }
134
134
  const timeout = options.timeout || Number(process.env.EXODUS_TEST_TIMEOUT) || 5000
135
135
  guard.promise = new Promise((resolve) => {
136
+ if (process.env.EXODUS_TEST_PLATFORM === 'engine262') {
137
+ // parallel timeouts are slowing down everything on engine262
138
+ // so we let only the host timeout to catch us, not individual test timeout
139
+ return
140
+ }
141
+
136
142
  guard.id = setTimeout(() => {
137
143
  guard.failed = true
138
144
  resolve()
@@ -304,13 +310,14 @@ class MockTimers {
304
310
  }
305
311
 
306
312
  async tickAsync(milliseconds = 1) {
307
- let shouldAwait = true
308
- for (let i = 0; i < milliseconds; i++) {
309
- if (shouldAwait) await awaitForMicrotaskQueue()
310
- this.#elapsed += 1
311
- shouldAwait = this.#microtick() !== null
312
- if (shouldAwait) while (this.#microtick() !== null);
313
+ const finish = this.#elapsed + milliseconds
314
+ await awaitForMicrotaskQueue()
315
+ while (this.#queue[0] && this.#queue[0].runAt <= finish) {
316
+ this.#elapsed = Math.max(this.#elapsed, this.#queue[0].runAt)
317
+ while (this.#microtick() !== null) await awaitForMicrotaskQueue()
313
318
  }
319
+
320
+ this.#elapsed = finish
314
321
  }
315
322
 
316
323
  #microtick() {
@@ -483,8 +490,10 @@ const awaitForMicrotaskQueue = async () => {
483
490
  // Do not rely on setTimeout here! it will tick actual time and is terribly slow (i.e. timers no longer fake)
484
491
  // 50_000 should be enough to flush everything that's going on in the microtask queue
485
492
  // E.g. JSC and SpiderMonkey hit this currently
493
+ // engine262 is extremely slow, tick just above 100 on it
486
494
  const promise = Promise.resolve()
487
- for (let i = 0; i < 50_000; i++) await promise
495
+ const tickPromiseRounds = process.env.EXODUS_TEST_PLATFORM === 'engine262' ? 110 : 50_000
496
+ for (let i = 0; i < tickPromiseRounds; i++) await promise
488
497
  }
489
498
 
490
499
  let builtinModules = []
package/src/exodus.js CHANGED
@@ -5,6 +5,29 @@ import { timersTrack, timersList, timersDebug, timersAssert } from './timers-tra
5
5
  import { insideEsbuild } from './dark.cjs'
6
6
  import { haveValidTimers } from './version.js'
7
7
 
8
+ const timersSpeedup = (rate, { apis = ['setTimeout', 'setInterval', 'Date'] } = {}) => {
9
+ if (!(typeof rate === 'number' && rate > 0)) throw new TypeError('Expected a positive rate')
10
+ const { setTimeout, setInterval, Date: OrigDate } = globalThis
11
+ for (const api of apis) {
12
+ // eslint-disable-next-line unicorn/prefer-switch
13
+ if (api === 'setTimeout') {
14
+ globalThis.setTimeout = (fn, ms, ...args) => setTimeout(fn, Math.ceil(ms / rate), ...args)
15
+ } else if (api === 'setInterval') {
16
+ globalThis.setInterval = (fn, ms, ...args) => setInterval(fn, Math.ceil(ms / rate), ...args)
17
+ } else if (api === 'Date') {
18
+ const base = OrigDate.now()
19
+ globalThis.Date = class Date extends OrigDate {
20
+ static now = () => base + Math.floor((OrigDate.now() - base) * rate)
21
+ constructor(first = globalThis.Date.now(), ...rest) {
22
+ super(first, ...rest)
23
+ }
24
+ }
25
+ } else {
26
+ throw new Error(`Unknown or unsupported API in timersSpeedup(): ${api}`)
27
+ }
28
+ }
29
+ }
30
+
8
31
  const isBundle = process.env.EXODUS_TEST_ENVIRONMENT === 'bundle' // TODO: improve mocking from bundle
9
32
  export const exodus = {
10
33
  __proto__: null,
@@ -21,7 +44,7 @@ export const exodus = {
21
44
  concurrency: node.engine !== 'pure', // pure engine doesn't support concurrency
22
45
  },
23
46
  mock: {
24
- ...{ timersTrack, timersList, timersDebug, timersAssert }, // eslint-disable-line unicorn/no-useless-spread
47
+ ...{ timersTrack, timersList, timersDebug, timersAssert, timersSpeedup }, // eslint-disable-line unicorn/no-useless-spread
25
48
  ...{ fetchRecord, fetchReplay }, // eslint-disable-line unicorn/no-useless-spread
26
49
  ...{ websocketRecord, websocketReplay }, // eslint-disable-line unicorn/no-useless-spread
27
50
  },
package/src/jest.js CHANGED
@@ -204,6 +204,19 @@ node.afterEach(() => {
204
204
 
205
205
  if (process.env.EXODUS_TEST_PLATFORM !== 'deno' && globalThis.process) {
206
206
  // TODO: deno, other engines
207
+
208
+ const reportActivity = () => {
209
+ if (process.env.EXODUS_TEST_TIMERS_TRACK) timersDebug()
210
+ if (process?.getActiveResourcesInfo) {
211
+ const all = process.getActiveResourcesInfo().filter((r) => r !== 'PipeWrap')
212
+ if (all.length > 0) {
213
+ const entries = [...new Set(all)].map((k) => [k, all.filter((x) => x === k).length])
214
+ const pretty = prettyFormat(Object.fromEntries(entries), { min: true })
215
+ console.log(`Active resources: { ${pretty.slice(1, -1).replaceAll('"', '')} }`)
216
+ }
217
+ }
218
+ }
219
+
207
220
  // This doesn't work with async imported tests, so for inband, we delay
208
221
  const after = () => {
209
222
  jestTimers.useRealTimers()
@@ -212,7 +225,7 @@ if (process.env.EXODUS_TEST_PLATFORM !== 'deno' && globalThis.process) {
212
225
  // give everything additional (configurable) defaultTimeout time to finish, otherwide fail
213
226
  const timeout = defaultTimeout
214
227
  setTimeout(() => {
215
- if (process.env.EXODUS_TEST_TIMERS_TRACK) timersDebug()
228
+ reportActivity()
216
229
  console.error(`${prefix} additional ${timeout}ms. Terminating with a failure...`)
217
230
  process.exit(1)
218
231
  }, timeout).unref()
@@ -221,7 +234,7 @@ if (process.env.EXODUS_TEST_PLATFORM !== 'deno' && globalThis.process) {
221
234
  const warnTimeout = 5000
222
235
  if (warnTimeout < timeout + 1000) {
223
236
  setTimeout(() => {
224
- if (process.env.EXODUS_TEST_TIMERS_TRACK) timersDebug()
237
+ reportActivity()
225
238
  console.warn(`${prefix} ${warnTimeout}ms. Waiting for ${timeout}ms to pass to finish...`)
226
239
  }, warnTimeout).unref()
227
240
  }
@@ -52,9 +52,11 @@ export function useFakeTimers({ doNotFake = doNotFakeDefault, ...rest } = {}) {
52
52
  return this
53
53
  }
54
54
 
55
+ const runAllTimersTime = 100_000_000_000 // > 3 years
56
+ const runAllTimersSplit = { step: 50_000_000, steps: 2000, last: 0 }
55
57
  export function runAllTimers() {
56
58
  assertEnabledTimers()
57
- advanceTimersByTime(100_000_000_000) // > 3 years
59
+ advanceTimersByTime(runAllTimersTime) // > 3 years
58
60
  return this
59
61
  }
60
62
 
@@ -95,7 +97,7 @@ export function advanceTimersByTime(time) {
95
97
  return this
96
98
  }
97
99
 
98
- const { step, steps, last } = splitTime(time)
100
+ const { step, steps, last } = time === runAllTimersTime ? runAllTimersSplit : splitTime(time)
99
101
  for (let i = 0; i < steps; i++) {
100
102
  if (!enabled) break // got disabled while looping
101
103
  mock.timers.tick(step)
@@ -122,7 +124,7 @@ export async function runOnlyPendingTimersAsync() {
122
124
  export async function advanceTimersByTimeAsync(time) {
123
125
  assertEnabledTimers()
124
126
  if (mock.timers.tickAsync) {
125
- await mock.timers.tickAsync(time)
127
+ await mock.timers.tickAsync(time) // runs microtasks at start and end
126
128
  } else {
127
129
  const { step, steps, last } = splitTime(time)
128
130
  for (let i = 0; i < steps; i++) {
@@ -133,9 +135,9 @@ export async function advanceTimersByTimeAsync(time) {
133
135
 
134
136
  if (last > 0 && enabled) await awaitForMicrotaskQueue()
135
137
  if (last > 0 && enabled) mock.timers.tick(last)
138
+ await awaitForMicrotaskQueue() // jest doc is misleading and it also does this after running timers
136
139
  }
137
140
 
138
- await awaitForMicrotaskQueue() // jest doc is misleading and it also does this after running timers
139
141
  return this
140
142
  }
141
143