From aa8c1587ff0cd431cdeb0e83ba79b0d7c1749cc1 Mon Sep 17 00:00:00 2001 From: Daniel Fry Date: Wed, 1 Jul 2026 19:32:00 +0100 Subject: [PATCH] fix(native): provide SourceCode.scriptURL so getDevServer resolves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RN's getDevServer (Libraries/Core/Devtools/getDevServer.js) reads SourceCode.getConstants().scriptURL and calls .match() on it. Under the native engine the value was undefined, so getDevServer threw and took down any test whose module graph reached it. Return a file:// (bundled) URL for the SourceCode native module in the boundary's device constants. It is deliberately NOT an http(s) URL: getDevServer only treats http(s) script URLs as a live dev server, so a file:// value keeps bundleLoadedFromServer false — tests run as if loaded from a bundle, not a Metro dev server. That prevents RN internals (AssetSourceResolver, symbolicateStackTrace, DevTools) and third-party SDKs from believing they're connected to a packager and attempting real network I/O against localhost:8081, mirroring the intent of RN's own jest mock. --- .changeset/native-sourcecode-scripturl.md | 5 ++++ .../vitest-native/src/native/boundary.mjs | 17 ++++++++++++ .../tests-native/dev-server.test.ts | 27 +++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 .changeset/native-sourcecode-scripturl.md create mode 100644 packages/vitest-native/tests-native/dev-server.test.ts diff --git a/.changeset/native-sourcecode-scripturl.md b/.changeset/native-sourcecode-scripturl.md new file mode 100644 index 0000000..cb942b8 --- /dev/null +++ b/.changeset/native-sourcecode-scripturl.md @@ -0,0 +1,5 @@ +--- +"vitest-native": patch +--- + +Provide `SourceCode.getConstants().scriptURL` at the native boundary. RN's `getDevServer` (`Libraries/Core/Devtools/getDevServer.js`) reads `scriptURL` and calls `.match()` on it; under the native engine the value was `undefined`, so `getDevServer` threw and took down any test whose module graph reached it. The boundary now returns a `file://` (bundled) URL for the `SourceCode` native module. It is deliberately not an `http(s)` URL: `getDevServer` only treats `http(s)` script URLs as a live dev server, so a `file://` value keeps `bundleLoadedFromServer` false — tests run as if loaded from a bundle rather than a Metro dev server, which prevents RN internals and third-party SDKs from believing they're connected to a packager and attempting real network I/O against `localhost:8081`. This mirrors the intent of RN's own Jest mock (which keeps that flag off). diff --git a/packages/vitest-native/src/native/boundary.mjs b/packages/vitest-native/src/native/boundary.mjs index 2001245..453db23 100644 --- a/packages/vitest-native/src/native/boundary.mjs +++ b/packages/vitest-native/src/native/boundary.mjs @@ -47,6 +47,23 @@ function deviceConstants(platform, reactNativeVersion) { doLeftAndRightSwapInRTL: true, localeIdentifier: "en_US", }, + // RN's getDevServer (Libraries/Core/Devtools/getDevServer.js) reads + // SourceCode.getConstants().scriptURL and calls .match() on it — undefined + // there throws and takes the whole file down. Expo's async-require + // (messageSocket) pulls this in on any Expo-core-importing test. Provide a + // defined string so the lookup resolves instead of crashing. + // + // Deliberately a file:// (bundled/release) URL, NOT http(s): getDevServer only + // treats http(s) scriptURLs as a live server, so a file:// value keeps + // `bundleLoadedFromServer` false — tests run as if loaded from a bundle, not a + // Metro dev server. That stops RN internals (AssetSourceResolver, + // symbolicateStackTrace, DevTools) and third-party SDKs from believing they're + // connected to a packager and attempting real network I/O against localhost:8081. + // Mirrors RN's own jest mock, which keeps this flag off (scriptURL: null there, + // but null would re-introduce the .match crash, so we use a bundled URL). + SourceCode: { + scriptURL: "file:///index.bundle", + }, }; } diff --git a/packages/vitest-native/tests-native/dev-server.test.ts b/packages/vitest-native/tests-native/dev-server.test.ts new file mode 100644 index 0000000..fc63e92 --- /dev/null +++ b/packages/vitest-native/tests-native/dev-server.test.ts @@ -0,0 +1,27 @@ +/** + * Proof: RN's `getDevServer` resolves under the native engine because the boundary + * provides `SourceCode.getConstants().scriptURL`. Without it, getDevServer calls + * `.match()` on `undefined` and throws — which under the native engine takes down + * any test whose module graph reaches it (Expo's async-require `messageSocket` + * pulls it in on Expo-core-importing suites). Surfaced by the obytes bake-off, + * where the whole login-form file failed to collect for this reason. + */ +import { describe, it, expect } from "vitest"; +// eslint-disable-next-line +import getDevServer from "react-native/Libraries/Core/Devtools/getDevServer"; + +describe("native engine: getDevServer / SourceCode.scriptURL", () => { + it("resolves instead of crashing on undefined scriptURL", () => { + // A non-string scriptURL would throw inside getDevServer (`.match`) before + // returning; reaching here at all proves the boundary provides a string. + const server = getDevServer(); + expect(typeof server.url).toBe("string"); + }); + + it("reports the bundle as NOT loaded from a dev server (no network I/O)", () => { + // The scriptURL is a file:// (bundled) URL, so getDevServer must not treat the + // test as connected to Metro — otherwise RN internals / third-party SDKs would + // attempt real fetches/WebSockets against localhost:8081. + expect(getDevServer().bundleLoadedFromServer).toBe(false); + }); +});