rnxsim 0.1.500 → 0.1.502
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 +1 -1
- package/cli/cloud-client.ts +90 -7
- package/cli/cloud-dispatch.ts +27 -27
- package/cli/cloud-session.ts +61 -6
- package/cli/commands/agent-wrapper.ts +17 -2
- package/cli/commands/control.ts +33 -31
- package/cli/commands/detox.ts +157 -159
- package/cli/commands/platform.ts +170 -48
- package/cli/commands/preview.ts +1 -1
- package/cli/commands/test.ts +403 -95
- package/cli/main.ts +11 -0
- package/cli/outbound-endpoints.ts +10 -0
- package/detox/jest-preset.cjs +1 -6
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +7 -1
- package/dist-lib/cloud-contract.mjs +5 -1
- package/dist-lib/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/detox/jest-preset.cjs +1 -6
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +54 -29
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +39 -12
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +6 -8
- package/dist-lib/sdk.mjs +6 -8
- package/dist-lib/skills.cjs +109 -51
- package/dist-lib/vite.cjs +1 -1
- package/package.json +1 -1
- package/skills/rnx-test/SKILL.md +2 -2
- package/src/cloud-contract.ts +2 -0
- package/src/sim-client.ts +8 -10
package/cli/commands/detox.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
// rnx detox — run existing detox suites against rnx
|
|
2
2
|
//
|
|
3
|
-
// the
|
|
4
|
-
//
|
|
5
|
-
// 2. makes sure an rnx shell is reachable (auto-launches one if not)
|
|
6
|
-
// 3. spawns jest with our preset baked in so `import ... from 'detox'`
|
|
7
|
-
// resolves to the rnx driver
|
|
8
|
-
//
|
|
9
|
-
// it deliberately shells out to the user's own jest so their jest version,
|
|
10
|
-
// transforms, and setupFiles keep working. the only thing we force is the
|
|
11
|
-
// moduleNameMapper for `^detox$`.
|
|
3
|
+
// use the customer's jest config unchanged. the rnx preset maps the detox
|
|
4
|
+
// driver and owns browser cleanup; jest owns setup, selection and deadlines.
|
|
12
5
|
|
|
13
6
|
import { spawn } from 'child_process'
|
|
14
7
|
import {
|
|
@@ -20,27 +13,17 @@ import {
|
|
|
20
13
|
unlinkSync,
|
|
21
14
|
} from 'fs'
|
|
22
15
|
import { createRequire } from 'module'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
23
17
|
import { tmpdir } from 'os'
|
|
24
18
|
import { dirname, resolve, join, relative } from 'path'
|
|
25
19
|
import { rnxPublicBrand } from '../../src/public-brand'
|
|
26
20
|
import { resolveCliAuth } from '../auth'
|
|
27
21
|
import { registerRun, shouldRegisterRun } from '../run-registry'
|
|
28
22
|
import { rnxExit } from '../run-rnx'
|
|
23
|
+
import { buildShellUrl } from './control'
|
|
29
24
|
import { discoverSootsimUrl } from './flow'
|
|
30
25
|
import { resolveDefaultUploadOrigin } from './upload'
|
|
31
26
|
|
|
32
|
-
// resolve the detox preset directory via the package's own subpath export.
|
|
33
|
-
function resolveDetoxDir(): string {
|
|
34
|
-
try {
|
|
35
|
-
const entry = import.meta.resolve(`${rnxPublicBrand.packageName}/detox`)
|
|
36
|
-
// import.meta.resolve returns a file:// URL — convert to a path
|
|
37
|
-
return dirname(entry.startsWith('file://') ? entry.slice(7) : entry)
|
|
38
|
-
} catch {
|
|
39
|
-
// fallback for source-layout dev (no compiled package.json exports)
|
|
40
|
-
return resolve(import.meta.dirname!, '..', '..', 'detox')
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
27
|
interface RunDetoxOpts {
|
|
45
28
|
port?: number
|
|
46
29
|
verbose?: boolean
|
|
@@ -73,62 +56,73 @@ export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise
|
|
|
73
56
|
}
|
|
74
57
|
|
|
75
58
|
const getFlag = (name: string) => {
|
|
59
|
+
const inline = args.find((arg) => arg.startsWith(`${name}=`))
|
|
60
|
+
if (inline) return inline.slice(name.length + 1)
|
|
76
61
|
const i = args.indexOf(name)
|
|
77
62
|
return i >= 0 ? args[i + 1] : undefined
|
|
78
63
|
}
|
|
79
64
|
const hasFlag = (name: string) => args.includes(name)
|
|
80
65
|
|
|
81
|
-
const watch = hasFlag('--watch')
|
|
66
|
+
const watch = hasFlag('--watch') || hasFlag('--watchAll')
|
|
82
67
|
const headed = hasFlag('--headed')
|
|
83
68
|
const noLaunch = hasFlag('--no-launch')
|
|
84
69
|
const configFlag = getFlag('--config')
|
|
85
|
-
const port =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const prev = args[i - 1]
|
|
91
|
-
if (
|
|
92
|
-
prev === '--config' ||
|
|
93
|
-
prev === '-t' ||
|
|
94
|
-
prev === '--testNamePattern' ||
|
|
95
|
-
prev === '--grep' ||
|
|
96
|
-
prev === '--outputFile' ||
|
|
97
|
-
prev === '--maxWorkers' ||
|
|
98
|
-
prev === '--shard'
|
|
70
|
+
const port =
|
|
71
|
+
Number(getFlag('--port')) || opts.port || Number(process.env.SOOTSIM_PORT) || 5173
|
|
72
|
+
if (getFlag('--sim')) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
'Detox owns its browser context; pin its shell with RNX_URL and --device instead of --sim',
|
|
99
75
|
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// consume only rnx options. jest must receive its complete argv, including
|
|
79
|
+
// selection, timeouts, reporters, coverage, and options added by future jest versions.
|
|
80
|
+
const jestArgs: string[] = ['jest']
|
|
81
|
+
for (let index = 0; index < args.length; index++) {
|
|
82
|
+
const arg = args[index]
|
|
83
|
+
if (arg === '--headed' || arg === '--no-launch') continue
|
|
84
|
+
const name = arg.split('=', 1)[0]
|
|
85
|
+
if (['--config', '--app', '--device', '--port'].includes(name)) {
|
|
86
|
+
if (!arg.includes('=')) {
|
|
87
|
+
if (!args[index + 1] || args[index + 1].startsWith('-')) {
|
|
88
|
+
throw new Error(`${name} requires a value`)
|
|
89
|
+
}
|
|
90
|
+
index++
|
|
91
|
+
}
|
|
92
|
+
continue
|
|
113
93
|
}
|
|
114
|
-
|
|
115
|
-
}
|
|
94
|
+
jestArgs.push(arg === '--grep' ? '--testNamePattern' : arg)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let localConfig =
|
|
98
|
+
configFlag ||
|
|
99
|
+
firstExisting([
|
|
100
|
+
'rnx-detox.config.cjs',
|
|
101
|
+
'jest.config.ts',
|
|
102
|
+
'jest.config.mts',
|
|
103
|
+
'jest.config.mjs',
|
|
104
|
+
'jest.config.cjs',
|
|
105
|
+
'jest.config.js',
|
|
106
|
+
'jest.config.cts',
|
|
107
|
+
'jest.config.json',
|
|
108
|
+
])
|
|
109
|
+
if (!localConfig && existsSync('package.json')) {
|
|
110
|
+
const pkg = JSON.parse(readFileSync('package.json', 'utf8'))
|
|
111
|
+
if (pkg.jest) localConfig = 'package.json'
|
|
112
|
+
}
|
|
116
113
|
|
|
117
|
-
// test dir discovery.
|
|
118
|
-
//
|
|
114
|
+
// test dir discovery. external test suites set RNX_TEST_DIR; otherwise
|
|
115
|
+
// we auto-detect the conventional locations.
|
|
119
116
|
const detoxDirs = ['e2e', 'test/e2e', 'detox']
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
?
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
? resolve(process.cwd(), process.env.RNX_TEST_DIR)
|
|
130
|
-
: null
|
|
131
|
-
if (!testDir) {
|
|
117
|
+
let testDir: string | null = process.env.RNX_TEST_DIR
|
|
118
|
+
? resolve(process.cwd(), process.env.RNX_TEST_DIR)
|
|
119
|
+
: null
|
|
120
|
+
if (!testDir && !localConfig && args[0] && !args[0].startsWith('-')) {
|
|
121
|
+
const target = resolve(args[0])
|
|
122
|
+
if (existsSync(target))
|
|
123
|
+
testDir = statSync(target).isDirectory() ? target : dirname(target)
|
|
124
|
+
}
|
|
125
|
+
if (!testDir && !localConfig) {
|
|
132
126
|
for (const dir of detoxDirs) {
|
|
133
127
|
const full = resolve(process.cwd(), dir)
|
|
134
128
|
if (existsSync(full)) {
|
|
@@ -137,13 +131,14 @@ export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise
|
|
|
137
131
|
}
|
|
138
132
|
}
|
|
139
133
|
}
|
|
140
|
-
if (!testDir) {
|
|
134
|
+
if (!testDir && !localConfig) {
|
|
141
135
|
console.error(
|
|
142
136
|
` error: no detox tests found. expected one of: ${detoxDirs.join(', ')}\n` +
|
|
143
137
|
` run 'rnx detox init' to scaffold a starter suite.`,
|
|
144
138
|
)
|
|
145
139
|
rnxExit(1)
|
|
146
140
|
}
|
|
141
|
+
testDir ??= process.cwd()
|
|
147
142
|
|
|
148
143
|
// auto-launch: make sure an rnx shell is reachable before running jest.
|
|
149
144
|
// Reuse the Maestro discovery path: scan local dev servers.
|
|
@@ -152,52 +147,40 @@ export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise
|
|
|
152
147
|
await ensureShellRunning(port)
|
|
153
148
|
}
|
|
154
149
|
|
|
155
|
-
// build the jest command.
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
const jestArgs: string[] = ['jest']
|
|
159
|
-
const localConfig =
|
|
160
|
-
configFlag ||
|
|
161
|
-
firstExisting(['rnx-detox.config.cjs', 'jest.config.cjs', 'jest.config.js'])
|
|
162
|
-
|
|
150
|
+
// build the jest command. prefer the user's config if they passed --config
|
|
151
|
+
// or if a rnx-detox.config.cjs exists in cwd. otherwise write a temporary
|
|
152
|
+
// config that loads our preset.
|
|
163
153
|
let tempConfig: string | null = null
|
|
164
|
-
const presetPath = resolve(resolveDetoxDir(), 'jest-preset.cjs')
|
|
165
154
|
if (localConfig) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
tempConfig = join(dirname(localConfigPath), `rnx-detox-${process.pid}.config.cjs`)
|
|
170
|
-
writeFileSync(
|
|
171
|
-
tempConfig,
|
|
172
|
-
`const rnx = require(${JSON.stringify(presetPath)})\n` +
|
|
173
|
-
`const customer = require(${JSON.stringify(localConfigPath)})\n` +
|
|
174
|
-
`module.exports = {\n` +
|
|
175
|
-
` ...rnx,\n` +
|
|
176
|
-
` ...customer,\n` +
|
|
177
|
-
` moduleNameMapper: { ...rnx.moduleNameMapper, ...customer.moduleNameMapper, '^detox$': rnx.moduleNameMapper['^detox$'] },\n` +
|
|
178
|
-
` setupFilesAfterEnv: [...new Set([...(customer.setupFilesAfterEnv || []), ...(rnx.setupFilesAfterEnv || [])])],\n` +
|
|
179
|
-
`}\n`,
|
|
155
|
+
jestArgs.push(
|
|
156
|
+
'--config',
|
|
157
|
+
localConfig.startsWith('{') ? localConfig : resolve(process.cwd(), localConfig),
|
|
180
158
|
)
|
|
181
|
-
jestArgs.push('--config', tempConfig)
|
|
182
159
|
} else {
|
|
183
160
|
// no local config — write a temp config that pulls in our preset.
|
|
184
161
|
// jest's --preset flag has quirky module resolution that breaks with
|
|
185
162
|
// subpath exports, so we generate a real config file instead.
|
|
186
163
|
// scope roots + testMatch to the discovered test dir so jest doesn't
|
|
187
164
|
// scan the entire repo (which causes haste collisions in monorepos).
|
|
165
|
+
const presetPath = fileURLToPath(
|
|
166
|
+
import.meta.resolve(`${rnxPublicBrand.packageName}/detox/jest-preset`),
|
|
167
|
+
)
|
|
188
168
|
// an external suite may optionally declare its own ignore patterns via e2e/jest-ignore.cjs
|
|
189
169
|
const suiteIgnoreFile = testDir ? join(testDir, 'e2e', 'jest-ignore.cjs') : null
|
|
190
170
|
let suiteIgnorePatterns: string[] = []
|
|
191
171
|
if (suiteIgnoreFile && existsSync(suiteIgnoreFile)) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
172
|
+
const req = createRequire(import.meta.url)
|
|
173
|
+
const exported = req(suiteIgnoreFile)
|
|
174
|
+
const patterns = Array.isArray(exported)
|
|
175
|
+
? exported
|
|
176
|
+
: exported.testPathIgnorePatterns
|
|
177
|
+
if (
|
|
178
|
+
!Array.isArray(patterns) ||
|
|
179
|
+
!patterns.every((pattern) => typeof pattern === 'string')
|
|
180
|
+
) {
|
|
181
|
+
throw new Error(`${suiteIgnoreFile} must export string ignore patterns`)
|
|
182
|
+
}
|
|
183
|
+
suiteIgnorePatterns = patterns
|
|
201
184
|
}
|
|
202
185
|
tempConfig = join(tmpdir(), `rnx-detox-${process.pid}.config.cjs`)
|
|
203
186
|
const cfg = {
|
|
@@ -215,36 +198,33 @@ export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise
|
|
|
215
198
|
jestArgs.push('--config', tempConfig)
|
|
216
199
|
}
|
|
217
200
|
|
|
218
|
-
if (watch) jestArgs.push('--watch')
|
|
219
|
-
|
|
220
|
-
const testNamePattern =
|
|
221
|
-
getFlag('-t') || getFlag('--testNamePattern') || getFlag('--grep')
|
|
222
|
-
if (testNamePattern) jestArgs.push('-t', testNamePattern)
|
|
223
|
-
|
|
224
201
|
// hosted run registration (org dashboard pass/fail row). when it applies,
|
|
225
202
|
// capture jest's structured results via --json --outputFile so the row can
|
|
226
203
|
// carry test counts + the first failure; console output stays untouched.
|
|
227
204
|
const auth = resolveCliAuth()
|
|
228
205
|
const wantsRegistration = !watch && shouldRegisterRun({ uploadedShare: false, auth })
|
|
229
|
-
let resultsFile: string | null =
|
|
230
|
-
|
|
231
|
-
if (process.env.
|
|
206
|
+
let resultsFile: string | null =
|
|
207
|
+
getFlag('--outputFile') || process.env.SOOTSIM_JEST_OUTPUT_FILE || null
|
|
208
|
+
if ((process.env.SOOTSIM_JEST_JSON === '1' || wantsRegistration) && !hasFlag('--json'))
|
|
209
|
+
jestArgs.push('--json')
|
|
210
|
+
if (!getFlag('--outputFile') && process.env.SOOTSIM_JEST_OUTPUT_FILE) {
|
|
232
211
|
jestArgs.push('--outputFile', process.env.SOOTSIM_JEST_OUTPUT_FILE)
|
|
233
|
-
} else if (wantsRegistration) {
|
|
212
|
+
} else if (!resultsFile && wantsRegistration) {
|
|
234
213
|
resultsFile = join(tmpdir(), `rnx-detox-results-${process.pid}.json`)
|
|
235
|
-
jestArgs.push('--
|
|
214
|
+
jestArgs.push('--outputFile', resultsFile)
|
|
236
215
|
}
|
|
237
216
|
|
|
238
|
-
jestArgs.push(...passthroughJestFlags)
|
|
239
|
-
|
|
240
|
-
// any positional test files get passed straight through
|
|
241
|
-
jestArgs.push(...positional)
|
|
242
|
-
|
|
243
217
|
console.log(` rnx detox`)
|
|
244
218
|
console.log(` test dir: ${testDir}`)
|
|
245
219
|
console.log(` port: ${port}${headed ? ' (headed)' : ''}`)
|
|
246
220
|
if (localConfig) console.log(` config: ${localConfig}`)
|
|
247
221
|
|
|
222
|
+
const shellBaseUrl = process.env.RNX_URL || `http://localhost:${port}`
|
|
223
|
+
const app = getFlag('--app')
|
|
224
|
+
const shellUrl = new URL(app ? await buildShellUrl(app, shellBaseUrl) : shellBaseUrl)
|
|
225
|
+
const device = getFlag('--device')
|
|
226
|
+
if (device) shellUrl.searchParams.set('device', device)
|
|
227
|
+
|
|
248
228
|
// proof capture runs under the repo's pinned node runtime. its v8 collector
|
|
249
229
|
// crashes in ClearStaleLeftTrimmedPointerVisitor when Sparkplug baseline
|
|
250
230
|
// compilation (Builtins_BaselineOutOfLinePrologue) pushes untagged register
|
|
@@ -273,7 +253,7 @@ export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise
|
|
|
273
253
|
RNX_TEST_DIR: testDir,
|
|
274
254
|
SOOTSIM_PORT: String(port),
|
|
275
255
|
SOOTSIM_HEADED: headed ? '1' : '',
|
|
276
|
-
RNX_URL:
|
|
256
|
+
RNX_URL: shellUrl.toString(),
|
|
277
257
|
},
|
|
278
258
|
})
|
|
279
259
|
|
|
@@ -292,26 +272,30 @@ export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise
|
|
|
292
272
|
process.on('SIGINT', onInt)
|
|
293
273
|
|
|
294
274
|
const startedAt = Date.now()
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
275
|
+
let code: number
|
|
276
|
+
try {
|
|
277
|
+
code = await new Promise<number>((resolveCode, reject) => {
|
|
278
|
+
child.once('error', reject)
|
|
279
|
+
child.once('exit', (exitCode, signal) => {
|
|
280
|
+
if (signal) {
|
|
281
|
+
console.error(` error: jest terminated by ${signal}`)
|
|
282
|
+
resolveCode(1)
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
resolveCode(exitCode ?? 1)
|
|
286
|
+
})
|
|
304
287
|
})
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
unlinkSync(tempConfig)
|
|
311
|
-
} catch {}
|
|
288
|
+
} finally {
|
|
289
|
+
process.off('SIGTERM', onTerm)
|
|
290
|
+
process.off('SIGINT', onInt)
|
|
291
|
+
if (tempConfig) unlinkSync(tempConfig)
|
|
292
|
+
}
|
|
312
293
|
|
|
313
294
|
if (wantsRegistration) {
|
|
314
|
-
const results =
|
|
295
|
+
const results =
|
|
296
|
+
resultsFile && existsSync(resultsFile) && statSync(resultsFile).mtimeMs >= startedAt
|
|
297
|
+
? readJestResults(resultsFile)
|
|
298
|
+
: null
|
|
315
299
|
const origin = await resolveDefaultUploadOrigin()
|
|
316
300
|
const run = await registerRun({
|
|
317
301
|
origin,
|
|
@@ -330,11 +314,20 @@ export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise
|
|
|
330
314
|
auth,
|
|
331
315
|
})
|
|
332
316
|
if (run) console.log(` run: ${run.id}`)
|
|
317
|
+
else rnxExit(code || 1)
|
|
318
|
+
}
|
|
319
|
+
if (
|
|
320
|
+
resultsFile &&
|
|
321
|
+
existsSync(resultsFile) &&
|
|
322
|
+
!getFlag('--outputFile') &&
|
|
323
|
+
!process.env.SOOTSIM_JEST_OUTPUT_FILE
|
|
324
|
+
) {
|
|
325
|
+
unlinkSync(resultsFile)
|
|
333
326
|
}
|
|
334
327
|
rnxExit(code)
|
|
335
328
|
}
|
|
336
329
|
|
|
337
|
-
type JestRunSummary = {
|
|
330
|
+
export type JestRunSummary = {
|
|
338
331
|
numTotalTests: number
|
|
339
332
|
numPassedTests: number
|
|
340
333
|
firstFailure: string | null
|
|
@@ -344,47 +337,52 @@ type JestRunSummary = {
|
|
|
344
337
|
// parse jest's --json output file down to the counts + first failure the
|
|
345
338
|
// hosted run row carries. tolerant: any parse problem returns null and the
|
|
346
339
|
// run registers with exit-code status only.
|
|
347
|
-
function readJestResults(file: string): JestRunSummary | null {
|
|
340
|
+
export function readJestResults(file: string): JestRunSummary | null {
|
|
348
341
|
try {
|
|
349
|
-
const parsed = JSON.parse(readFileSync(file, 'utf8'))
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
342
|
+
const parsed: unknown = JSON.parse(readFileSync(file, 'utf8'))
|
|
343
|
+
if (
|
|
344
|
+
!parsed ||
|
|
345
|
+
typeof parsed !== 'object' ||
|
|
346
|
+
!('numTotalTests' in parsed) ||
|
|
347
|
+
typeof parsed.numTotalTests !== 'number' ||
|
|
348
|
+
!('numPassedTests' in parsed) ||
|
|
349
|
+
typeof parsed.numPassedTests !== 'number' ||
|
|
350
|
+
!('testResults' in parsed) ||
|
|
351
|
+
!Array.isArray(parsed.testResults)
|
|
352
|
+
)
|
|
353
|
+
return null
|
|
360
354
|
let firstFailure: string | null = null
|
|
361
355
|
let firstFailedIndex: number | null = null
|
|
362
356
|
let index = 0
|
|
363
|
-
for (const suite of parsed.testResults
|
|
364
|
-
|
|
357
|
+
for (const suite of parsed.testResults) {
|
|
358
|
+
if (!suite || typeof suite !== 'object' || !Array.isArray(suite.assertionResults))
|
|
359
|
+
return null
|
|
360
|
+
for (const test of suite.assertionResults) {
|
|
361
|
+
if (!test || typeof test !== 'object') return null
|
|
365
362
|
if (firstFailedIndex == null && test.status === 'failed') {
|
|
366
363
|
firstFailedIndex = index
|
|
367
|
-
const
|
|
368
|
-
|
|
364
|
+
const messages: unknown[] = Array.isArray(test.failureMessages)
|
|
365
|
+
? test.failureMessages
|
|
366
|
+
: []
|
|
367
|
+
const message = messages
|
|
368
|
+
.filter((value): value is string => typeof value === 'string')
|
|
369
|
+
.join('\n')
|
|
370
|
+
firstFailure =
|
|
371
|
+
[typeof test.fullName === 'string' ? test.fullName : '', message]
|
|
372
|
+
.filter(Boolean)
|
|
373
|
+
.join(': ') || null
|
|
369
374
|
}
|
|
370
375
|
index += 1
|
|
371
376
|
}
|
|
372
377
|
}
|
|
373
|
-
if (typeof parsed.numTotalTests !== 'number') return null
|
|
374
378
|
return {
|
|
375
379
|
numTotalTests: parsed.numTotalTests,
|
|
376
|
-
numPassedTests: parsed.numPassedTests
|
|
380
|
+
numPassedTests: parsed.numPassedTests,
|
|
377
381
|
firstFailure,
|
|
378
382
|
firstFailedIndex,
|
|
379
383
|
}
|
|
380
384
|
} catch {
|
|
381
385
|
return null
|
|
382
|
-
} finally {
|
|
383
|
-
if (!process.env.SOOTSIM_JEST_OUTPUT_FILE) {
|
|
384
|
-
try {
|
|
385
|
-
unlinkSync(file)
|
|
386
|
-
} catch {}
|
|
387
|
-
}
|
|
388
386
|
}
|
|
389
387
|
}
|
|
390
388
|
|
|
@@ -419,7 +417,7 @@ function initDetoxProject(): void {
|
|
|
419
417
|
|
|
420
418
|
/** @type {import('@jest/types').Config.InitialOptions} */
|
|
421
419
|
module.exports = {
|
|
422
|
-
preset: 'rnxsim/detox
|
|
420
|
+
preset: 'rnxsim/detox',
|
|
423
421
|
rootDir: __dirname,
|
|
424
422
|
testMatch: ['<rootDir>/e2e/**/*.test.ts', '<rootDir>/e2e/**/*.test.js'],
|
|
425
423
|
}
|