feat(FN-4250): ensure FileBrowserProvider wraps all App branches with tests
Refactored App.tsx to ensure FileBrowserProvider wraps all render branches, with a new test verifying the coverage. A design doc was added to document the approach, and vitest config received a minor adjustment. Fusion-Task-Id: FN-4250
This commit is contained in:
5
.changeset/FN-4250-chatview-filebrowser-provider.md
Normal file
5
.changeset/FN-4250-chatview-filebrowser-provider.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix clickable file paths reliability by ensuring `FileBrowserProvider` wraps every render branch in `AppInner`, including the loader branch.
|
||||||
@@ -1503,15 +1503,6 @@ function AppInner() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!initialLoadComplete) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DashboardLoader stage={loadingStage} />
|
|
||||||
<ToastContainer toasts={toasts} onRemove={removeToast} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const showOnboardingResumeCard = !modalManager.modelOnboardingOpen && isOnboardingResumable();
|
const showOnboardingResumeCard = !modalManager.modelOnboardingOpen && isOnboardingResumable();
|
||||||
const showPostOnboardingRecommendations =
|
const showPostOnboardingRecommendations =
|
||||||
!modalManager.modelOnboardingOpen &&
|
!modalManager.modelOnboardingOpen &&
|
||||||
@@ -1522,7 +1513,14 @@ function AppInner() {
|
|||||||
return (
|
return (
|
||||||
<NavigationHistoryProvider value={{ pushNav, replaceCurrent }}>
|
<NavigationHistoryProvider value={{ pushNav, replaceCurrent }}>
|
||||||
<FileBrowserProvider openFile={openFileInBrowser}>
|
<FileBrowserProvider openFile={openFileInBrowser}>
|
||||||
<Header
|
{!initialLoadComplete ? (
|
||||||
|
<>
|
||||||
|
<DashboardLoader stage={loadingStage} />
|
||||||
|
<ToastContainer toasts={toasts} onRemove={removeToast} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Header
|
||||||
shellHost={shellHost.host}
|
shellHost={shellHost.host}
|
||||||
onOpenSettings={openSettingsWithNav}
|
onOpenSettings={openSettingsWithNav}
|
||||||
onOpenGitHubImport={openGitHubImportWithNav}
|
onOpenGitHubImport={openGitHubImportWithNav}
|
||||||
@@ -1783,22 +1781,24 @@ function AppInner() {
|
|||||||
onReopenOnboarding={reopenOnboardingWithNav}
|
onReopenOnboarding={reopenOnboardingWithNav}
|
||||||
/>
|
/>
|
||||||
<AuthTokenRecoveryDialog open={authTokenRecoveryOpen} />
|
<AuthTokenRecoveryDialog open={authTokenRecoveryOpen} />
|
||||||
{shellApi && (
|
{shellApi && (
|
||||||
<>
|
<>
|
||||||
<NativeShellOnboardingModal
|
<NativeShellOnboardingModal
|
||||||
open={requiresShellOnboarding}
|
open={requiresShellOnboarding}
|
||||||
shellApi={shellApi}
|
shellApi={shellApi}
|
||||||
shellState={shellState}
|
shellState={shellState}
|
||||||
onComplete={() => setShellOnboardingComplete(true)}
|
onComplete={() => setShellOnboardingComplete(true)}
|
||||||
/>
|
/>
|
||||||
<NativeShellConnectionManager
|
<NativeShellConnectionManager
|
||||||
open={shellConnectionManagerOpen}
|
open={shellConnectionManagerOpen}
|
||||||
shellApi={shellApi}
|
shellApi={shellApi}
|
||||||
shellState={shellState}
|
shellState={shellState}
|
||||||
onClose={() => setShellConnectionManagerOpen(false)}
|
onClose={() => setShellConnectionManagerOpen(false)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</FileBrowserProvider>
|
</FileBrowserProvider>
|
||||||
</NavigationHistoryProvider>
|
</NavigationHistoryProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,9 +3,16 @@ import { render, screen, fireEvent, waitFor, act, within } from "@testing-librar
|
|||||||
import type { NodeConfig, Settings } from "@fusion/core";
|
import type { NodeConfig, Settings } from "@fusion/core";
|
||||||
import type { ProjectInfo } from "../../api";
|
import type { ProjectInfo } from "../../api";
|
||||||
import { scopedKey } from "../../utils/projectStorage";
|
import { scopedKey } from "../../utils/projectStorage";
|
||||||
|
import { useFileBrowser } from "../../context/FileBrowserContext";
|
||||||
|
|
||||||
// No mock needed - tests use localStorage directly
|
// No mock needed - tests use localStorage directly
|
||||||
|
|
||||||
|
function FileBrowserProbe({ testId }: { testId: string }) {
|
||||||
|
const ctx = useFileBrowser();
|
||||||
|
const status = ctx && typeof ctx.openFile === "function" ? "ok" : "missing";
|
||||||
|
return <div data-testid={testId}>{status}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
const defaultSettings: Settings = {
|
const defaultSettings: Settings = {
|
||||||
maxConcurrent: 2,
|
maxConcurrent: 2,
|
||||||
maxWorktrees: 4,
|
maxWorktrees: 4,
|
||||||
@@ -346,6 +353,14 @@ vi.mock("../../components/TodoView", () => ({
|
|||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../components/ChatView", () => ({
|
||||||
|
ChatView: () => <FileBrowserProbe testId="fb-probe-chat" />,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../components/DashboardLoader", () => ({
|
||||||
|
DashboardLoader: () => <FileBrowserProbe testId="fb-probe-loader" />,
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock("../../components/QuickChatFAB", () => ({
|
vi.mock("../../components/QuickChatFAB", () => ({
|
||||||
QuickChatFAB: () => null,
|
QuickChatFAB: () => null,
|
||||||
}));
|
}));
|
||||||
@@ -585,6 +600,7 @@ beforeEach(() => {
|
|||||||
mockRefreshProjects.mockReset();
|
mockRefreshProjects.mockReset();
|
||||||
mockRefreshProjects.mockImplementation(async () => {});
|
mockRefreshProjects.mockImplementation(async () => {});
|
||||||
mockCurrentProjectState.currentProject = { id: DEFAULT_PROJECT_ID, name: "Test Project", path: "/test", status: "active" as const, isolationMode: "in-process" as const, createdAt: "", updatedAt: "" };
|
mockCurrentProjectState.currentProject = { id: DEFAULT_PROJECT_ID, name: "Test Project", path: "/test", status: "active" as const, isolationMode: "in-process" as const, createdAt: "", updatedAt: "" };
|
||||||
|
mockCurrentProjectState.loading = false;
|
||||||
mockCurrentProjectState.setCurrentProject.mockClear();
|
mockCurrentProjectState.setCurrentProject.mockClear();
|
||||||
mockCurrentProjectState.clearCurrentProject.mockClear();
|
mockCurrentProjectState.clearCurrentProject.mockClear();
|
||||||
// Reset node context mocks
|
// Reset node context mocks
|
||||||
@@ -646,6 +662,29 @@ beforeEach(() => {
|
|||||||
mockUseViewportMode.mockReturnValue("desktop");
|
mockUseViewportMode.mockReturnValue("desktop");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("FN-4250 FileBrowserProvider coverage", () => {
|
||||||
|
it("FN-4250: ChatView branch is inside FileBrowserProvider", async () => {
|
||||||
|
localStorage.setItem(taskViewStorageKey(), "chat");
|
||||||
|
|
||||||
|
render(<App />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("fb-probe-chat")).toHaveTextContent("ok");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("FN-4250: loader branch is inside FileBrowserProvider", async () => {
|
||||||
|
mockProjectsState.loading = true;
|
||||||
|
mockCurrentProjectState.loading = true;
|
||||||
|
|
||||||
|
render(<App />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("fb-probe-loader")).toHaveTextContent("ok");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("App backend-unreachable first-run flow", () => {
|
describe("App backend-unreachable first-run flow", () => {
|
||||||
it("renders backend connection error page instead of setup wizard when projects fetch fails during first-run", async () => {
|
it("renders backend connection error page instead of setup wizard when projects fetch fails during first-run", async () => {
|
||||||
mockProjectsState.projects = [];
|
mockProjectsState.projects = [];
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const qualityAppTests = [
|
|||||||
"app/api/**/*.test.ts",
|
"app/api/**/*.test.ts",
|
||||||
// Representative workflow/component coverage. Exhaustive modal/view suites
|
// Representative workflow/component coverage. Exhaustive modal/view suites
|
||||||
// stay available in the full `dashboard-app` project.
|
// stay available in the full `dashboard-app` project.
|
||||||
"app/components/__tests__/{ActiveAgentsPanel,AgentMentionPopup,AgentMetricsBar,AgentReflectionsTab,AgentTokenStatsPanel,AuthTokenRecoveryDialog,Board,board-mobile-view-switch,ChatView,Column,ConfirmDialog,ConversationHistory,DashboardLoader,DirectoryPicker,ErrorBoundary,ExecutorStatusBar,FileBrowser,FileEditor,InlineCreateCard,LoginInstructions,MemoryView,MessageComposer,MobileNavBar,NewTaskModal,NodeCard,NodeHealthDot,NodeStatusIndicator,ProjectCard,ProjectSelector,ProviderIcon,QuickChatFAB,ResearchView,StashRecoveryView,TaskCard,TaskChangesTab,TaskComments,TaskDetailModal.github-tracking-header,TaskDocumentsTab,TaskForm,TaskIdIntegrityBanner,ThemeSelectorSwatchContract,WorkflowResultsTab}.test.tsx",
|
"app/components/__tests__/{ActiveAgentsPanel,AgentMentionPopup,AgentMetricsBar,AgentReflectionsTab,AgentTokenStatsPanel,App,AuthTokenRecoveryDialog,Board,board-mobile-view-switch,ChatView,Column,ConfirmDialog,ConversationHistory,DashboardLoader,DirectoryPicker,ErrorBoundary,ExecutorStatusBar,FileBrowser,FileEditor,InlineCreateCard,LoginInstructions,MemoryView,MessageComposer,MobileNavBar,NewTaskModal,NodeCard,NodeHealthDot,NodeStatusIndicator,ProjectCard,ProjectSelector,ProviderIcon,QuickChatFAB,ResearchView,StashRecoveryView,TaskCard,TaskChangesTab,TaskComments,TaskDetailModal.github-tracking-header,TaskDocumentsTab,TaskForm,TaskIdIntegrityBanner,ThemeSelectorSwatchContract,WorkflowResultsTab}.test.tsx",
|
||||||
// Hooks and utilities are fast, user-visible state/formatting behavior.
|
// Hooks and utilities are fast, user-visible state/formatting behavior.
|
||||||
"app/context/**/*.test.tsx",
|
"app/context/**/*.test.tsx",
|
||||||
"app/hooks/__tests__/{useAgents,useAgentLogs,useAppSettings,useAuthOnboarding,useConfirm,useCurrentProject,useNodes,useNodeSettingsSync,useProjects,useQuickChat,useTasks,useTerminalSessions,useTheme,useToast,useUsageData,useViewState}.test.{ts,tsx}",
|
"app/hooks/__tests__/{useAgents,useAgentLogs,useAppSettings,useAuthOnboarding,useConfirm,useCurrentProject,useNodes,useNodeSettingsSync,useProjects,useQuickChat,useTasks,useTerminalSessions,useTheme,useToast,useUsageData,useViewState}.test.{ts,tsx}",
|
||||||
|
|||||||
Reference in New Issue
Block a user