@exodus/test 1.0.0-rc.76 → 1.0.0-rc.78

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
@@ -18,8 +18,8 @@ It can run your existing tests on [all runtimes and also browsers](#engines), wi
18
18
  - Actual `expect` module, also `jest-extended` and `jest-when` just work on top
19
19
  - Snapshots, including snapshot matchers
20
20
  - Function and timer mocks
21
- - [test.concurrent]()
22
- - Module mocks (on top of Node.js runtime only), including for ESM modules
21
+ - [test.concurrent](https://jestjs.io/docs/api#testconcurrentname-fn-timeout)
22
+ - Module mocks, including for ESM modules (already loaded ESM modules can be mocked only on `node:test`)
23
23
  - Loads Jest configuration
24
24
  - It works on Hermes too!
25
25
  - Built-in network record/replay for offline tests, mocking `fetch` and `WebSocket` sessions
package/bin/browsers.js CHANGED
@@ -43,7 +43,20 @@ async function newPage(runner, browser, { binary, dropNetwork }) {
43
43
  return newPage(runner, browser, { binary, dropNetwork })
44
44
  }
45
45
 
46
- await page.goto('file:///dev/null') // Need to load a secure origin for e.g. crypto.subtle to be available
46
+ // Need to load a secure origin for e.g. crypto.subtle to be available
47
+ if (runner === 'playwright' && binary === 'webkit') {
48
+ // Can attempt to download /dev/null, so we apply a work-around
49
+ await page.route('https://www.secure-context-top-level-domain-for-tests/*', (route) =>
50
+ route.fulfill({
51
+ status: 200,
52
+ contentType: 'text/html; charset=utf-8',
53
+ body: '<!doctype html><html><body></body></html>',
54
+ })
55
+ )
56
+ await page.goto('https://www.secure-context-top-level-domain-for-tests/')
57
+ } else {
58
+ await page.goto('file:///dev/null')
59
+ }
47
60
 
48
61
  if (dropNetwork && context.setOffline) await context.setOffline(true)
49
62
  if (dropNetwork && page.setOfflineMode) await page.setOfflineMode(true)
package/bin/inband.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { resolve } from 'node:path'
2
- import { describe } from '../src/engine.js'
2
+ import { describe, after } from '../src/engine.js'
3
3
 
4
4
  const files = JSON.parse(process.env.EXODUS_TEST_INBAND)
5
5
  if (!Array.isArray(files)) throw new Error('Unexpected')
@@ -9,3 +9,5 @@ for (const file of files.sort()) {
9
9
  await import(resolve(file))
10
10
  })
11
11
  }
12
+
13
+ if (globalThis.EXODUS_TEST_AFTER_INBAND) after(globalThis.EXODUS_TEST_AFTER_INBAND)
package/bin/index.js CHANGED
@@ -572,7 +572,7 @@ if (options.pure) {
572
572
  const missUnhandled = ['jsc'].includes(options.platform) || options.browsers
573
573
  if (missUnhandled) warnHuman(`Warning: ${engineName} does not have unhandled rejections tracking`)
574
574
 
575
- const runOne = async (inputFile) => {
575
+ const runOne = async (inputFile, attempt = 0) => {
576
576
  const bundled = buildFile ? await buildFile(inputFile) : undefined
577
577
  if (buildFile) assert(bundled.file)
578
578
  const file = buildFile ? bundled.file : inputFile
@@ -589,6 +589,11 @@ if (options.pure) {
589
589
  const ms = Number(process.hrtime.bigint() - start) / 1e6
590
590
  return { ok: code === 0, output: [stdout, stderr], ms }
591
591
  } catch (err) {
592
+ if (options.engine === 'xs:bundle' && err.signal === 'SIGSEGV' && attempt < 2) {
593
+ // xs sometimes randomly crashes with SIGSEGV on CI. Allow 3 attempts (allow 0 - 1 to fail)
594
+ return runOne(inputFile, attempt + 1)
595
+ }
596
+
592
597
  const ms = Number(process.hrtime.bigint() - start) / 1e6
593
598
  const { code, stdout = '', stderr = '', signal, killed } = err
594
599
  if (code === null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/test",
3
- "version": "1.0.0-rc.76",
3
+ "version": "1.0.0-rc.78",
4
4
  "author": "Exodus Movement, Inc.",
5
5
  "description": "A test suite runner",
6
6
  "homepage": "https://github.com/ExodusMovement/test",
package/src/jest.js CHANGED
@@ -12,13 +12,14 @@ import { format as prettyFormat } from 'pretty-format'
12
12
 
13
13
  const { getCallerLocation, installLocationInNextTest } = createCallerLocationHook()
14
14
 
15
- let addStatefulApis = true
15
+ let inband = false
16
16
  if (process.env.EXODUS_TEST_ENVIRONMENT !== 'bundle') {
17
- // We can't provide snapshots in inband tests yet, and mocks/timers are unsafe there
18
17
  const files = process.argv.slice(1)
19
- if (files.length === 1 && files[0].endsWith('/inband.js')) addStatefulApis = false
18
+ if (files.length === 1 && files[0].endsWith('/inband.js')) inband = true
20
19
  }
21
20
 
21
+ // We can't provide snapshots in inband tests yet, and mocks/timers are unsafe there
22
+ const addStatefulApis = !inband
22
23
  if (addStatefulApis) setupSnapshots(expect)
23
24
 
24
25
  let defaultTimeout = Number(process.env.EXODUS_TEST_TIMEOUT) || jestConfig().testTimeout // overridable via jest.setTimeout()
@@ -199,7 +200,8 @@ node.afterEach(() => {
199
200
 
200
201
  if (process.env.EXODUS_TEST_PLATFORM !== 'deno' && globalThis.process) {
201
202
  // TODO: deno, other engines
202
- node.after(() => {
203
+ // This doesn't work with async imported tests, so for inband, we delay
204
+ const after = () => {
203
205
  jestTimers.useRealTimers()
204
206
  const prefix = `Tests completed, but still have asynchronous activity after`
205
207
 
@@ -217,7 +219,13 @@ if (process.env.EXODUS_TEST_PLATFORM !== 'deno' && globalThis.process) {
217
219
  console.warn(`${prefix} ${warnTimeout}ms. Waiting for ${timeout}ms to pass to finish...`)
218
220
  }, warnTimeout).unref()
219
221
  }
220
- })
222
+ }
223
+
224
+ if (inband) {
225
+ globalThis.EXODUS_TEST_AFTER_INBAND = after
226
+ } else {
227
+ node.after(after)
228
+ }
221
229
  }
222
230
 
223
231
  export const jest = {
@@ -66,8 +66,14 @@ export function runOnlyPendingTimers() {
66
66
  }
67
67
 
68
68
  export function advanceTimersByTime(time) {
69
- assert(Number.isSafeInteger(time) && time > 0)
69
+ assert(Number.isSafeInteger(time) && time >= 0)
70
70
  assertEnabledTimers()
71
+
72
+ if (time === 0) {
73
+ mock.timers.tick(0)
74
+ return this
75
+ }
76
+
71
77
  // We split this into multiple steps to run timers scheduled during the time we are running
72
78
  const minSteps = Math.min(1000, time) // usually just split e.g. 5 seconds into 1000 * 5ms
73
79
  const step = Number(Math.floor(time / minSteps).toPrecision(1))