From c6df5b4a327d5b53c48df149ad1ad1069a9ba612 Mon Sep 17 00:00:00 2001 From: Zortos <65777760+zortos293@users.noreply.github.com> Date: Fri, 4 Sep 2026 22:43:41 +0200 Subject: [PATCH] fix(web): defer browser discovery in integrations --- .../settings/IntegrationsSettings.test.tsx | 65 +++++++++++++++++++ .../settings/IntegrationsSettings.tsx | 7 +- 2 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 apps/web/src/components/settings/IntegrationsSettings.test.tsx diff --git a/apps/web/src/components/settings/IntegrationsSettings.test.tsx b/apps/web/src/components/settings/IntegrationsSettings.test.tsx new file mode 100644 index 000000000000..1a8dc7d6aae8 --- /dev/null +++ b/apps/web/src/components/settings/IntegrationsSettings.test.tsx @@ -0,0 +1,65 @@ +import { DEFAULT_CLIENT_SETTINGS, DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts"; +import { act, StrictMode, type ReactNode } from "react"; +import { create, type ReactTestRenderer } from "react-test-renderer"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; + +const { listBrowserImportSources } = vi.hoisted(() => ({ + listBrowserImportSources: vi.fn().mockResolvedValue([]), +})); + +vi.mock("../preview/previewBridge", () => ({ + previewBridge: { listBrowserImportSources }, +})); +vi.mock("../../env", () => ({ isElectron: true })); +vi.mock("../../state/environments", () => ({ + useEnvironments: () => ({ environments: [], isReady: true }), + usePrimaryEnvironment: () => null, +})); +vi.mock("../../hooks/useSettings", () => ({ + PRIMARY_SETTINGS_UNAVAILABLE_MESSAGE: "Connect to an environment", + useClientSettings: (selector: (settings: typeof DEFAULT_CLIENT_SETTINGS) => unknown) => + selector(DEFAULT_CLIENT_SETTINGS), + useClientSettingsHydrated: () => true, + usePrimarySettingsAvailable: () => true, + usePrimarySettings: () => DEFAULT_UNIFIED_SETTINGS, + useUpdatePrimarySettings: () => vi.fn(), +})); +vi.mock("./settingsLayout", async (importOriginal) => ({ + ...(await importOriginal()), + SettingsPageContainer: ({ children }: { children: ReactNode }) => children, +})); + +import { IntegrationsSettingsPanel } from "./IntegrationsSettings"; + +let renderer: ReactTestRenderer | undefined; + +beforeEach(() => { + vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); + listBrowserImportSources.mockClear(); +}); + +afterEach(async () => { + await act(() => renderer?.unmount()); + vi.unstubAllGlobals(); +}); + +async function openSettings() { + await act(() => { + renderer = create( + + + , + ); + }); +} + +describe("Integrations browser discovery", () => { + it("does not scan browser files when entering or revisiting settings", async () => { + await openSettings(); + expect(listBrowserImportSources).not.toHaveBeenCalled(); + + await act(() => renderer?.unmount()); + await openSettings(); + expect(listBrowserImportSources).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/web/src/components/settings/IntegrationsSettings.tsx b/apps/web/src/components/settings/IntegrationsSettings.tsx index 5b1d9ba5e21e..44cb3ee30c28 100644 --- a/apps/web/src/components/settings/IntegrationsSettings.tsx +++ b/apps/web/src/components/settings/IntegrationsSettings.tsx @@ -36,7 +36,7 @@ import { } from "@t3tools/contracts"; import { PREVIEW_VIEWPORT_PRESETS } from "@t3tools/shared/previewViewport"; import { InfoIcon, MoreVertical, Plus as PlusIcon } from "lucide-react"; -import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"; +import { useCallback, useRef, useState, type ReactNode } from "react"; import { ScreenRotationIcon } from "~/browser/ScreenRotationIcon"; import { resolveEnvironmentOptionLabel } from "~/components/BranchToolbar.logic"; @@ -802,11 +802,6 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) { .catch(() => setSources((previous) => previous ?? [])); }, []); - // Loaded once so the first open is instant instead of flashing a spinner. - useEffect(() => { - loadSources(); - }, [loadSources]); - // Runs one import for the wizard. A new profile is registered only once the // import succeeds — the cookies land in its partition first — so a blocked // attempt never leaves an empty profile behind.