@exodus/test 1.0.0-rc.112 → 1.0.0-rc.114

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
@@ -28,7 +28,7 @@ It can run your existing tests on [all runtimes and also browsers](#engines), wi
28
28
  [![JavaScriptCore](https://img.shields.io/badge/JavaScriptCore-006CFF?style=flat-square)](https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html)
29
29
  [![SpiderMonkey](https://img.shields.io/badge/SpiderMonkey-FFD681?style=flat-square)](https://spidermonkey.dev/)
30
30
  [![QuickJS](https://img.shields.io/badge/QuickJS-E58200?style=flat-square)](https://github.com/quickjs-ng/quickjs)
31
- [![XS](https://img.shields.io/badge/XS-0B307A?style=flat-square)](https://github.com/Moddable-OpenSource/moddable-xst)
31
+ [![XS](https://img.shields.io/badge/XS-0B307A?style=flat-square)](https://github.com/Moddable-OpenSource/moddable)
32
32
  [![GraalJS](https://img.shields.io/badge/GraalJS-C74634?style=flat-square)](https://github.com/oracle/graaljs)
33
33
  [![Boa](https://img.shields.io/badge/Boa-F3FF00?style=flat-square)](https://github.com/boa-dev/boa)
34
34
  [![Escargot](https://img.shields.io/badge/Escargot-1428A0?style=flat-square)](https://github.com/Samsung/escargot)
@@ -174,7 +174,7 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
174
174
  - `hermes:bundle` — [Hermes](https://hermesengine.dev) (React Native JavaScript engine)
175
175
  - `spidermonkey:bundle` — [SpiderMonkey](https://spidermonkey.dev/) (Firefox/Gecko JavaScript engine)
176
176
  - `quickjs:bundle` — [QuickJS](https://github.com/quickjs-ng/quickjs)
177
- - `xs:bundle` — [XS](https://github.com/Moddable-OpenSource/moddable-xst)
177
+ - `xs:bundle` — [Moddable XS](https://github.com/Moddable-OpenSource/moddable)
178
178
  - `graaljs:bundle` — [GraalJS](https://github.com/oracle/graaljs)
179
179
  - `escargot:bundle` — [Escargot](https://github.com/Samsung/escargot)
180
180
  - `boa:bundle` — [Boa](https://github.com/boa-dev/boa)
@@ -69,6 +69,8 @@ function findBinaryOnce(name) {
69
69
  return require('electron')
70
70
  case 'workerd':
71
71
  return require.resolve('workerd/bin/workerd')
72
+ case 'porffor':
73
+ return require.resolve('porffor/porf')
72
74
  case 'jerryscript':
73
75
  name = 'jerry' // look under this name, including in global
74
76
  return findFile([jsvu, esvu])
package/bin/index.js CHANGED
@@ -55,6 +55,7 @@ const ENGINES = new Map(
55
55
  'boa:bundle': { binary: 'boa', binaryArgs: ['-m'], ...bareboneOpts },
56
56
  'nova:bundle': { binary: 'nova', binaryArgs: ['eval'], ...bareboneOpts },
57
57
  'jerryscript:bundle': { binary: 'jerryscript', ...bareboneOpts },
58
+ 'porffor:bundle': { binary: 'porffor', ...bareboneOpts }, // blocked on https://github.com/CanadaHonk/porffor/issues/176
58
59
  // Special case: running a browser from CLI like a bundle
59
60
  'servo:bundle': { binary: 'servo', binaryArgs: ['--headless'], ...bundleOpts, html: true },
60
61
  'workerd:bundle': { binary: 'workerd', binaryArgs: ['test'], ...bundleOpts, workerd: true },
@@ -71,8 +72,8 @@ const ENGINES = new Map(
71
72
  })
72
73
  )
73
74
  const bareOk = ['v8', 'd8', 'spidermonkey', 'quickjs', 'xs', 'hermes', 'shermes']
74
- const bareNotrack = ['jsc', 'escargot', 'boa', 'graaljs', 'jerry', 'engine262', 'servo', 'workerd']
75
- const bareIncomplete = ['ladybird-js', 'nova', 'duk']
75
+ const bareNotrack = ['jsc', 'escargot', 'boa', 'graaljs', 'jerryscript', 'engine262', 'servo']
76
+ const bareIncomplete = ['ladybird-js', 'nova', 'duktape']
76
77
 
77
78
  const getEnvFlag = (name) => {
78
79
  if (!Object.hasOwn(process.env, name)) return
@@ -662,7 +663,7 @@ async function launch(binary, args, opts = {}, buffering = false) {
662
663
  }
663
664
 
664
665
  const barebones = [...bareOk, ...bareNotrack, ...bareIncomplete]
665
- assertBinary(binary, ['node', 'bun', 'deno', 'electron', 'workerd', ...barebones])
666
+ assertBinary(binary, ['node', 'bun', 'deno', 'electron', 'workerd', 'jerry', 'duk', ...barebones])
666
667
  if (binary === c8 && process.platform === 'win32') {
667
668
  ;[binary, args] = ['node', [binary, ...args]]
668
669
  }
@@ -693,7 +694,7 @@ if (options.pure) {
693
694
 
694
695
  setEnv('EXODUS_TEST_CONTEXT', 'pure')
695
696
  const missUnhandled = bareNotrack.includes(options.platform) || isBrowserLike
696
- const isIncomplete = bareIncomplete.includes(options.platform)
697
+ const isIncomplete = bareIncomplete.includes(options.platform) || options.platform === 'workerd'
697
698
  if (missUnhandled) warnHuman(`Warning: ${engineName} does not have unhandled rejections tracking`)
698
699
  if (isIncomplete) warnHuman(`Warning: ${engineName} support is incomplete`)
699
700
 
@@ -711,18 +712,20 @@ if (options.pure) {
711
712
  bundled.fileWrapper = `${bundled.file}.wrapper.js`
712
713
  bundled.fileConfig = `${bundled.file}.capnp`
713
714
  assert(/^[a-z0-9/_.-]+\.js$/iu.test(bundled.file), bundled.file)
715
+ const { compatibilityDate } = await import('workerd')
714
716
  const jsRelativePath = basename(bundled.file)
715
717
  const wrapperRelativePath = basename(bundled.fileWrapper)
716
718
  const wrapperContent = `
717
719
  export default {
718
720
  async test(ctrl, env, ctx) {
719
721
  await import('./' + ${JSON.stringify(jsRelativePath)})
720
- let exitCode = EXODUS_TEST_PROCESS.exitCode // can fail early
721
- if (exitCode === 0) {
722
- if (typeof globalThis.EXODUS_TEST_RUN !== 'function') throw new Error('node:test not loaded')
723
- exitCode = await globalThis.EXODUS_TEST_RUN()
722
+ await globalThis.EXODUS_TEST_LOAD()
723
+ if (globalThis.EXODUS_TEST_PROMISE) {
724
+ const exitCode = await globalThis.EXODUS_TEST_PROMISE
725
+ if (exitCode !== 0) throw new Error(\`Tests failed with exit code \${exitCode}\`)
726
+ } else {
727
+ console.log('WARNING: node:test not loaded, asynchronous tests might be missed')
724
728
  }
725
- if (exitCode !== 0) throw new Error(\`Tests failed with exit code \${exitCode}\`)
726
729
  }
727
730
  }`
728
731
  await writeFile(bundled.fileWrapper, wrapperContent)
@@ -734,7 +737,7 @@ const mainWorker :Workerd.Worker = (
734
737
  (name = ${JSON.stringify(wrapperRelativePath)}, esModule = embed ${JSON.stringify(wrapperRelativePath)}),
735
738
  (name = ${JSON.stringify(jsRelativePath)}, esModule = embed ${JSON.stringify(jsRelativePath)}),
736
739
  ],
737
- compatibilityDate = "2024-01-01",
740
+ compatibilityDate = ${JSON.stringify(compatibilityDate)},
738
741
  );`
739
742
  await writeFile(bundled.fileConfig, configContent)
740
743
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/test",
3
- "version": "1.0.0-rc.112",
3
+ "version": "1.0.0-rc.114",
4
4
  "author": "Exodus Movement, Inc.",
5
5
  "description": "A test suite runner",
6
6
  "homepage": "https://github.com/ExodusOSS/test",
@@ -181,8 +181,8 @@
181
181
  },
182
182
  "optionalDependencies": {
183
183
  "@chalker/queue": "^1.0.1",
184
- "@exodus/replay": "^1.0.0-rc.10",
185
- "@exodus/test-bundler": "1.0.0-rc.13",
184
+ "@exodus/replay": "^1.0.0-rc.11",
185
+ "@exodus/test-bundler": "1.0.0-rc.15",
186
186
  "c8": "^9.1.0",
187
187
  "expect": "^30.2.0",
188
188
  "fast-glob": "^3.2.11",
@@ -210,7 +210,7 @@
210
210
  "jsvu": "^3.0.3",
211
211
  "prettier": "^3.0.3",
212
212
  "typedoc": "^0.28.16",
213
- "workerd": "^1.20260205.0"
213
+ "workerd": "^1.20260207.0"
214
214
  },
215
215
  "peerDependencies": {
216
216
  "@babel/register": "^7.0.0",
@@ -15,7 +15,7 @@ let willstart
15
15
 
16
16
  const abstractProcess = globalThis.process || globalThis.EXODUS_TEST_PROCESS
17
17
 
18
- if (process.env.EXODUS_TEST_IS_BROWSER) {
18
+ if (process.env.EXODUS_TEST_IS_BROWSER || process.env.EXODUS_TEST_PLATFORM === 'workerd') {
19
19
  globalThis.EXODUS_TEST_PROMISE = new Promise((resolve) => (abstractProcess._exitHook = resolve))
20
20
  if (!abstractProcess._maybeProcessExitCode && abstractProcess === globalThis.process) {
21
21
  // Electron with Node.js integration has real process
@@ -30,7 +30,7 @@ const check = (condition, message) => {
30
30
 
31
31
  function parseArgs(args) {
32
32
  check(args.length <= 3)
33
- const name = typeof args[0] === 'string' ? args.shift() : 'test'
33
+ const name = typeof args[0] === 'string' ? args.shift() : undefined
34
34
  const fn = args.pop()
35
35
  const options = args.pop() || {}
36
36
  return { name, options, fn }
@@ -46,6 +46,7 @@ class Context {
46
46
  #hooks
47
47
 
48
48
  constructor(parent, name, options = {}) {
49
+ if (!name || typeof name !== 'string') name = '<anonymous>'
49
50
  Object.assign(this, { root: parent?.root, parent, name, options })
50
51
  this.#fullName = parent && parent !== parent.root ? `${parent.fullName} > ${name}` : name
51
52
  if (this.#fullName === name) this.#fullName = this.#fullName.replace(INBAND_PREFIX_REGEX, '')
@@ -104,7 +105,7 @@ function enterContext(name, options) {
104
105
  function exitContext() {
105
106
  check(context !== context.root)
106
107
  context = context.parent
107
- if (context === context.root && run !== globalThis.EXODUS_TEST_RUN) willstart = setTimeout(run, 0)
108
+ if (context === context.root) willstart = setTimeout(run, 0)
108
109
  }
109
110
 
110
111
  async function runFunction(fn, context) {
@@ -185,8 +186,6 @@ async function runContext(context) {
185
186
  async function run() {
186
187
  check(!running)
187
188
  running = true
188
- const manual = globalThis.EXODUS_TEST_RUN === run
189
- const res = manual ? new Promise((resolve) => (abstractProcess._exitHook = resolve)) : undefined
190
189
  check(context === context.root)
191
190
  await runContext(context).catch((error) => {
192
191
  // Should not throw under regular circumstances
@@ -195,12 +194,8 @@ async function run() {
195
194
  })
196
195
  // Let unhandled errors be processed (and set the error code)
197
196
  setTimeout(() => abstractProcess._maybeProcessExitCode?.(), 0)
198
- return res
199
197
  }
200
198
 
201
- // For workerd, expose run as a global so the wrapper can call it
202
- if (process.env.EXODUS_TEST_PLATFORM === 'workerd') globalThis.EXODUS_TEST_RUN = run
203
-
204
199
  async function describe(...args) {
205
200
  const { name, options, fn } = parseArgs(args)
206
201
  enterContext(name, options)
@@ -491,7 +486,7 @@ function getMacrotick() {
491
486
  const { scheduler, MessageChannel } = globalThis
492
487
  if (scheduler?.yield) return () => scheduler.yield()
493
488
  if (setImmediate) return () => new Promise((resolve) => setImmediate(resolve))
494
- if (MessageChannel) {
489
+ if (MessageChannel && !globalThis.Cloudflare) {
495
490
  return async () => {
496
491
  const { port1, port2 } = new MessageChannel()
497
492
  await new Promise((resolve) => {
@@ -569,7 +564,7 @@ if (process.env.EXODUS_TEST_ENVIRONMENT === 'bundle') {
569
564
 
570
565
  // eslint-disable-next-line no-undef
571
566
  let snapshotResolver = (dir, name) => [dir, `${name}.snapshot`] // default per Node.js docs
572
- let snapshotSerializers = [(obj) => JSON.stringify(obj, null, 2)]
567
+ let snapshotSerializers = [(obj) => (obj === undefined ? `${obj}` : JSON.stringify(obj, null, 2))]
573
568
  const serializeSnapshot = (obj) => {
574
569
  let val = obj
575
570
  for (const fn of snapshotSerializers) val = fn(val)
@@ -2,6 +2,7 @@ const nameCounts = new Map()
2
2
  let snapshotText, snapshotTextClean
3
3
 
4
4
  const escapeSnapshot = (str) => str.replaceAll(/([\\`]|\$\{)/gu, '\\$1')
5
+ const escapeSnapshotKey = (s) => escapeSnapshot(s).replaceAll('\n', '\\n').replaceAll('"', '\\"')
5
6
 
6
7
  function matchSnapshot(readSnapshot, assert, name, serialized) {
7
8
  // We don't have native snapshots, polyfill reading
@@ -25,12 +26,16 @@ function matchSnapshot(readSnapshot, assert, name, serialized) {
25
26
  nameCounts.set(name, count)
26
27
  const escaped = escapeSnapshot(serialized)
27
28
  const key = `${name} ${count}`
28
- const makeEntry = (x) => `\nexports[\`${escapeSnapshot(key)}\`] = \`${x}\`;\n`
29
- const fixedText = escaped.includes('\r') ? snapshotText : snapshotTextClean // well, if we expect \r let's preserve them
30
- const final = escaped.includes('\n') ? `\n${escaped}\n` : escaped
31
- if (fixedText.includes(makeEntry(final))) return
32
- // Perhaps wrapped with newlines from Node.js snapshots?
33
- if (!final.includes('\n') && fixedText.includes(makeEntry(`\n${final}\n`))) return
29
+ // Node.js and jest escape keys differently, both result to same strings, accept both
30
+ for (const keyfun of [escapeSnapshot, escapeSnapshotKey]) {
31
+ const makeEntry = (x) => `\nexports[\`${keyfun(key)}\`] = \`${x}\`;\n`
32
+ const fixedText = escaped.includes('\r') ? snapshotText : snapshotTextClean // well, if we expect \r let's preserve them
33
+ const final = escaped.includes('\n') ? `\n${escaped}\n` : escaped
34
+ if (fixedText.includes(makeEntry(final))) return
35
+ // Perhaps wrapped with newlines from Node.js snapshots?
36
+ if (!final.includes('\n') && fixedText.includes(makeEntry(`\n${final}\n`))) return
37
+ }
38
+
34
39
  return assert.fail(`Could not match "${key}" in snapshot. ${addFail}`)
35
40
  }
36
41