Open History to see the last commit
1import type { ReactNode } from "react";
2import { act } from "react";
3import { createRoot } from "react-dom/client";
4import { expect, it, vi } from "vite-plus/test";
5
6const { Children, harness } = vi.hoisted(() => ({
7 Children: ({ children }: { children?: ReactNode }) => <>{children}</>,
8 harness: {
9 // The app-wide error slot other features (composer sends, branch refreshes) write to.
10 pendingConnectionError: null as string | null,
11 connectPress: vi.fn(),
12 },
13}));
14
15vi.mock("react-native", () => ({
16 View: Children,
17 Pressable: Children,
18 Linking: { openSettings: vi.fn() },
19 StyleSheet: { absoluteFill: {} },
20}));
21vi.mock("react-native-svg", () => ({
22 default: Children,
23 Defs: Children,
24 RadialGradient: Children,
25 Rect: () => null,
26 Stop: () => null,
27}));
28vi.mock("react-native-safe-area-context", () => ({ useSafeAreaInsets: () => ({ bottom: 0 }) }));
29vi.mock("@react-navigation/native", () => ({
30 StackActions: { replace: vi.fn() },
31 useIsFocused: () => true,
32 useNavigation: () => ({ canGoBack: () => true, goBack: vi.fn(), dispatch: vi.fn() }),
33 useRoute: () => ({ name: "ConnectionsNew" }),
34}));
35vi.mock("@effect/atom-react", () => ({ useAtomValue: () => null }));
36vi.mock("@clerk/expo", () => ({ useAuth: () => ({ isLoaded: false, isSignedIn: false }) }));
37vi.mock("expo-camera", () => ({
38 CameraView: () => null,
39 useCameraPermissions: () => [null, vi.fn()],
40}));
41vi.mock("expo-haptics", () => ({
42 notificationAsync: vi.fn(async () => {}),
43 NotificationFeedbackType: { Success: "success", Error: "error" },
44}));
45vi.mock("../../components/ui/ListRow", () => ({ ListRow: () => null }));
46vi.mock("../../components/ui/Dialog", () => ({ alert: vi.fn() }));
47vi.mock("../../components/ui/ScreenScrollView", () => ({ ScreenScrollView: Children }));
48vi.mock("../../components/ui/AppText", () => ({
49 AppText: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
50 AppTextInput: () => null,
51}));
52vi.mock("../../components/ui/FrostedCutout", () => ({ FrostedCutout: () => null }));
53vi.mock("../../components/ui/AppSymbol", () => ({ SymbolView: () => null }));
54vi.mock("../../lib/useUniwindTheme", () => ({ useUniwindTheme: () => ({}) }));
55vi.mock("../settings/appearance/AppearancePreferencesProvider", () => ({
56 useAppearancePreferences: () => ({ themeAppearance: "dark" }),
57}));
58vi.mock("../settings/components/SettingsScreen", () => ({ SettingsScreen: Children }));
59vi.mock("../settings/settings-navigation", () => ({
60 leaveSettingsPage: vi.fn(),
61 openSettings: vi.fn(),
62}));
63vi.mock("../layout/workspace/workspace-selection-store", () => ({
64 workspaceHasColumns: () => false,
65}));
66vi.mock("../cloud/publicConfig", () => ({ hasCloudPublicConfig: () => false }));
67vi.mock("./CloudEnvironmentRows", () => ({ CloudEnvironmentRows: () => null }));
68vi.mock("./environmentSections", () => ({
69 splitEnvironmentSections: () => ({ connectedCloudEnvironments: [] }),
70}));
71vi.mock("./devAutoConnect", () => ({
72 autoConnectReport: () => ({ connecting: false, error: null }),
73 devAutoConnectAttemptAtom: {},
74}));
75vi.mock("../../state/use-remote-environment-registry", () => ({
76 setPendingConnectionError: (message: string | null) => {
77 harness.pendingConnectionError = message;
78 },
79 useRemoteConnections: () => ({
80 connectionPairingUrl: "",
81 connectedEnvironments: [],
82 pairingConnectionError: harness.pendingConnectionError,
83 onChangeConnectionPairingUrl: vi.fn(),
84 onConnectPress: harness.connectPress,
85 onSetEnvironmentEnabled: vi.fn(),
86 onRemoveEnvironmentPress: vi.fn(),
87 }),
88}));
89
90import { ConnectionsNewRouteScreen } from "./ConnectionsNewRouteScreen";
91
92it("does not show another feature's error as a pairing failure", () => {
93 (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
94 vi.stubGlobal("__DEV__", false);
95 harness.pendingConnectionError = "Could not send the message.";
96 const container = document.createElement("div");
97 const root = createRoot(container);
98 act(() => root.render(<ConnectionsNewRouteScreen route={{ params: undefined }} />));
99
100 expect(container.textContent).not.toContain("Could not send the message.");
101 expect(container.textContent).toContain("For machines on your local network or tailnet.");
102
103 act(() => root.unmount());
104 // Leaving the form must not wipe an error that belongs to another screen.
105 expect(harness.pendingConnectionError).toBe("Could not send the message.");
106});