|
| 1 | +import * as NodeModule from "node:module"; |
| 2 | +import { expect, it, vi } from "vite-plus/test"; |
| 3 | + |
| 4 | +const harness = vi.hoisted(() => { |
| 5 | + vi.stubGlobal("__DEV__", true); |
| 6 | + return { pair: vi.fn() }; |
| 7 | +}); |
| 8 | + |
| 9 | +// The native package pulls in React Native; its link parsing and actions live in core and routers. |
| 10 | +vi.mock("@react-navigation/native", () => { |
| 11 | + const requireFromNative = NodeModule.createRequire( |
| 12 | + NodeModule.createRequire(import.meta.url).resolve("@react-navigation/native/package.json"), |
| 13 | + ); |
| 14 | + const requireFromCore = NodeModule.createRequire( |
| 15 | + requireFromNative.resolve("@react-navigation/core/package.json"), |
| 16 | + ); |
| 17 | + const { getStateFromPath } = requireFromNative("@react-navigation/core"); |
| 18 | + const { CommonActions, StackActions } = requireFromCore("@react-navigation/routers"); |
| 19 | + return { getStateFromPath, CommonActions, StackActions }; |
| 20 | +}); |
| 21 | + |
| 22 | +vi.mock("./rootNavigation", () => ({ |
| 23 | + rootNavigationRef: { isReady: () => false }, |
| 24 | + whenRootNavigationReady: () => {}, |
| 25 | +})); |
| 26 | +vi.mock("../components/ui/Dialog", () => ({ alert: () => {} })); |
| 27 | +vi.mock("../features/prompt-links/prompt-link-actions", () => ({})); |
| 28 | +// Stores the prompt-link steps read only when a link carries a prompt; they load the native runtime. |
| 29 | +vi.mock("../state/projects", () => ({})); |
| 30 | +vi.mock("../state/pull-requests", () => ({})); |
| 31 | +vi.mock("../state/server", () => ({})); |
| 32 | +vi.mock("../state/threads", () => ({})); |
| 33 | +vi.mock("../state/use-composer-drafts", () => ({})); |
| 34 | +vi.mock("../state/workspace", () => ({})); |
| 35 | +vi.mock("../state/use-remote-environment-registry", () => ({ pairEnvironment: harness.pair })); |
| 36 | +vi.mock("../features/workspace/workspace-selection-store", () => ({ |
| 37 | + settingsLinkPage: () => undefined, |
| 38 | + workspaceHasColumns: () => false, |
| 39 | +})); |
| 40 | + |
| 41 | +import { findLinkedRoute } from "./appLinking"; |
| 42 | +import { getAppStateFromPath } from "./appLinkState"; |
| 43 | + |
| 44 | +const PAIRING_URL = "http://192.168.1.10:3773/pair#token=abc"; |
| 45 | + |
| 46 | +// RootStack imports every screen, which Node cannot load; these are its paths for the pairing form. |
| 47 | +const linking = { |
| 48 | + initialRouteName: "Home", |
| 49 | + screens: { |
| 50 | + Home: "", |
| 51 | + Settings: "settings", |
| 52 | + SettingsEnvironmentNew: "settings/environment-new", |
| 53 | + ConnectionsNew: "connections/new", |
| 54 | + }, |
| 55 | +}; |
| 56 | + |
| 57 | +it("a link cannot aim a pairing at a saved environment", () => { |
| 58 | + harness.pair.mockResolvedValue({ ok: false, error: null }); |
| 59 | + for (const path of ["connections/new", "settings/environment-new"]) { |
| 60 | + harness.pair.mockClear(); |
| 61 | + const state = getAppStateFromPath( |
| 62 | + `${path}?routeFor=victim&pairingUrl=${encodeURIComponent(PAIRING_URL)}&autoConnect=1`, |
| 63 | + linking, |
| 64 | + ); |
| 65 | + |
| 66 | + expect(findLinkedRoute(state, ["ConnectionsNew", "SettingsEnvironmentNew"])?.params).toEqual({ |
| 67 | + pairingUrl: PAIRING_URL, |
| 68 | + autoConnect: "1", |
| 69 | + }); |
| 70 | + expect(harness.pair).toHaveBeenCalledExactlyOnceWith(PAIRING_URL); |
| 71 | + } |
| 72 | +}); |
0 commit comments