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 showPostOnboardingRecommendations =
|
||||
!modalManager.modelOnboardingOpen &&
|
||||
@@ -1522,7 +1513,14 @@ function AppInner() {
|
||||
return (
|
||||
<NavigationHistoryProvider value={{ pushNav, replaceCurrent }}>
|
||||
<FileBrowserProvider openFile={openFileInBrowser}>
|
||||
<Header
|
||||
{!initialLoadComplete ? (
|
||||
<>
|
||||
<DashboardLoader stage={loadingStage} />
|
||||
<ToastContainer toasts={toasts} onRemove={removeToast} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Header
|
||||
shellHost={shellHost.host}
|
||||
onOpenSettings={openSettingsWithNav}
|
||||
onOpenGitHubImport={openGitHubImportWithNav}
|
||||
@@ -1783,22 +1781,24 @@ function AppInner() {
|
||||
onReopenOnboarding={reopenOnboardingWithNav}
|
||||
/>
|
||||
<AuthTokenRecoveryDialog open={authTokenRecoveryOpen} />
|
||||
{shellApi && (
|
||||
<>
|
||||
<NativeShellOnboardingModal
|
||||
open={requiresShellOnboarding}
|
||||
shellApi={shellApi}
|
||||
shellState={shellState}
|
||||
onComplete={() => setShellOnboardingComplete(true)}
|
||||
/>
|
||||
<NativeShellConnectionManager
|
||||
open={shellConnectionManagerOpen}
|
||||
shellApi={shellApi}
|
||||
shellState={shellState}
|
||||
onClose={() => setShellConnectionManagerOpen(false)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{shellApi && (
|
||||
<>
|
||||
<NativeShellOnboardingModal
|
||||
open={requiresShellOnboarding}
|
||||
shellApi={shellApi}
|
||||
shellState={shellState}
|
||||
onComplete={() => setShellOnboardingComplete(true)}
|
||||
/>
|
||||
<NativeShellConnectionManager
|
||||
open={shellConnectionManagerOpen}
|
||||
shellApi={shellApi}
|
||||
shellState={shellState}
|
||||
onClose={() => setShellConnectionManagerOpen(false)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</FileBrowserProvider>
|
||||
</NavigationHistoryProvider>
|
||||
);
|
||||
|
||||
@@ -3,9 +3,16 @@ import { render, screen, fireEvent, waitFor, act, within } from "@testing-librar
|
||||
import type { NodeConfig, Settings } from "@fusion/core";
|
||||
import type { ProjectInfo } from "../../api";
|
||||
import { scopedKey } from "../../utils/projectStorage";
|
||||
import { useFileBrowser } from "../../context/FileBrowserContext";
|
||||
|
||||
// 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 = {
|
||||
maxConcurrent: 2,
|
||||
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", () => ({
|
||||
QuickChatFAB: () => null,
|
||||
}));
|
||||
@@ -585,6 +600,7 @@ beforeEach(() => {
|
||||
mockRefreshProjects.mockReset();
|
||||
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.loading = false;
|
||||
mockCurrentProjectState.setCurrentProject.mockClear();
|
||||
mockCurrentProjectState.clearCurrentProject.mockClear();
|
||||
// Reset node context mocks
|
||||
@@ -646,6 +662,29 @@ beforeEach(() => {
|
||||
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", () => {
|
||||
it("renders backend connection error page instead of setup wizard when projects fetch fails during first-run", async () => {
|
||||
mockProjectsState.projects = [];
|
||||
|
||||
@@ -11,7 +11,7 @@ const qualityAppTests = [
|
||||
"app/api/**/*.test.ts",
|
||||
// Representative workflow/component coverage. Exhaustive modal/view suites
|
||||
// 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.
|
||||
"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}",
|
||||
|
||||
Reference in New Issue
Block a user