authorgravatar for zortosdev@proton.meZortos <zortosdev@proton.me> 2026-09-06 00:59:54+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-05 15:59:54-07:00
logc2cfe59ac356768deb2a7d3e3461c715aa50a7a1
tree0a2b6a7a73ef6236178d7962dfc4f7c03dc04164
parent89cc7434f0a5373f1b9c73ee579480e8f862fa5b
signature Signed by PGP key B5690EEEBB952194

fix(web): defer browser discovery in integrations (#9797)


2 files changed, 66 insertions(+), 6 deletions(-)

apps/web/src/components/settings/IntegrationsSettings.test.tsx created+65
...@@ -0,0 +1,65 @@
1import { DEFAULT_CLIENT_SETTINGS, DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts";
2import { act, StrictMode, type ReactNode } from "react";
3import { create, type ReactTestRenderer } from "react-test-renderer";
4import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
5
6const { listBrowserImportSources } = vi.hoisted(() => ({
7 listBrowserImportSources: vi.fn().mockResolvedValue([]),
8}));
9
10vi.mock("../preview/previewBridge", () => ({
11 previewBridge: { listBrowserImportSources },
12}));
13vi.mock("../../env", () => ({ isElectron: true }));
14vi.mock("../../state/environments", () => ({
15 useEnvironments: () => ({ environments: [], isReady: true }),
16 usePrimaryEnvironment: () => null,
17}));
18vi.mock("../../hooks/useSettings", () => ({
19 PRIMARY_SETTINGS_UNAVAILABLE_MESSAGE: "Connect to an environment",
20 useClientSettings: (selector: (settings: typeof DEFAULT_CLIENT_SETTINGS) => unknown) =>
21 selector(DEFAULT_CLIENT_SETTINGS),
22 useClientSettingsHydrated: () => true,
23 usePrimarySettingsAvailable: () => true,
24 usePrimarySettings: () => DEFAULT_UNIFIED_SETTINGS,
25 useUpdatePrimarySettings: () => vi.fn(),
26}));
27vi.mock("./settingsLayout", async (importOriginal) => ({
28 ...(await importOriginal<typeof import("./settingsLayout")>()),
29 SettingsPageContainer: ({ children }: { children: ReactNode }) => children,
30}));
31
32import { IntegrationsSettingsPanel } from "./IntegrationsSettings";
33
34let renderer: ReactTestRenderer | undefined;
35
36beforeEach(() => {
37 vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
38 listBrowserImportSources.mockClear();
39});
40
41afterEach(async () => {
42 await act(() => renderer?.unmount());
43 vi.unstubAllGlobals();
44});
45
46async function openSettings() {
47 await act(() => {
48 renderer = create(
49 <StrictMode>
50 <IntegrationsSettingsPanel />
51 </StrictMode>,
52 );
53 });
54}
55
56describe("Integrations browser discovery", () => {
57 it("does not scan browser files when entering or revisiting settings", async () => {
58 await openSettings();
59 expect(listBrowserImportSources).not.toHaveBeenCalled();
60
61 await act(() => renderer?.unmount());
62 await openSettings();
63 expect(listBrowserImportSources).not.toHaveBeenCalled();
64 });
65});
apps/web/src/components/settings/IntegrationsSettings.tsx+1-6
...@@ -36,7 +36,7 @@ import {...@@ -36,7 +36,7 @@ import {
36} from "@t3tools/contracts";36} from "@t3tools/contracts";
37import { PREVIEW_VIEWPORT_PRESETS } from "@t3tools/shared/previewViewport";37import { PREVIEW_VIEWPORT_PRESETS } from "@t3tools/shared/previewViewport";
38import { InfoIcon, MoreVertical, Plus as PlusIcon } from "lucide-react";38import { InfoIcon, MoreVertical, Plus as PlusIcon } from "lucide-react";
39import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";39import { useCallback, useRef, useState, type ReactNode } from "react";
4040
41import { ScreenRotationIcon } from "~/browser/ScreenRotationIcon";41import { ScreenRotationIcon } from "~/browser/ScreenRotationIcon";
42import { resolveEnvironmentOptionLabel } from "~/components/BranchToolbar.logic";42import { resolveEnvironmentOptionLabel } from "~/components/BranchToolbar.logic";
...@@ -804,11 +804,6 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {...@@ -804,11 +804,6 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
804 .catch(() => setSources((previous) => previous ?? []));804 .catch(() => setSources((previous) => previous ?? []));
805 }, []);805 }, []);
806806
807 // Loaded once so the first open is instant instead of flashing a spinner.
808 useEffect(() => {
809 loadSources();
810 }, [loadSources]);
811
812 // Runs one import for the wizard. A new profile is registered only once the807 // Runs one import for the wizard. A new profile is registered only once the
813 // import succeeds — the cookies land in its partition first — so a blocked808 // import succeeds — the cookies land in its partition first — so a blocked
814 // attempt never leaves an empty profile behind.809 // attempt never leaves an empty profile behind.