feat(FN-1613): merge fusion/fn-1613
This commit is contained in:
@@ -136,6 +136,7 @@ function AppInner() {
|
||||
toggleAutoMerge,
|
||||
toggleGlobalPause,
|
||||
toggleEnginePause,
|
||||
refresh: refreshAppSettings,
|
||||
} = useAppSettings(currentProject?.id);
|
||||
const {
|
||||
availableModels,
|
||||
@@ -542,6 +543,10 @@ function AppInner() {
|
||||
taskOperations={{ moveTask, deleteTask, mergeTask, retryTask, duplicateTask }}
|
||||
deepLink={{ handleDetailClose }}
|
||||
settings={{ githubTokenConfigured, themeMode, colorTheme, setThemeMode, setColorTheme }}
|
||||
onSettingsClose={() => {
|
||||
modalManager.closeSettings();
|
||||
void refreshAppSettings();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -53,6 +53,8 @@ interface AppModalsProps {
|
||||
setThemeMode: (mode: ThemeMode) => void;
|
||||
setColorTheme: (theme: ColorTheme) => void;
|
||||
};
|
||||
/** Optional override for the settings modal close handler. When provided, this is called instead of modalManager.closeSettings. */
|
||||
onSettingsClose?: () => void;
|
||||
}
|
||||
|
||||
export function AppModals({
|
||||
@@ -69,7 +71,11 @@ export function AppModals({
|
||||
taskOperations,
|
||||
deepLink,
|
||||
settings,
|
||||
onSettingsClose,
|
||||
}: AppModalsProps) {
|
||||
// Use the override handler if provided, otherwise fall back to modalManager.closeSettings
|
||||
const handleSettingsClose = onSettingsClose ?? modalManager.closeSettings;
|
||||
|
||||
return (
|
||||
<>
|
||||
{modalManager.detailTask && (
|
||||
@@ -96,7 +102,7 @@ export function AppModals({
|
||||
{modalManager.settingsOpen && (
|
||||
<ModalErrorBoundary>
|
||||
<SettingsModal
|
||||
onClose={modalManager.closeSettings}
|
||||
onClose={handleSettingsClose}
|
||||
addToast={addToast}
|
||||
initialSection={modalManager.settingsInitialSection}
|
||||
projectId={projectId}
|
||||
|
||||
@@ -83,4 +83,36 @@ describe("useAppSettings", () => {
|
||||
expect(result.current.globalPaused).toBe(true);
|
||||
expect(mockUpdateSettings).toHaveBeenCalledWith({ globalPause: false }, "proj_123");
|
||||
});
|
||||
|
||||
it("refresh() re-fetches and updates state", async () => {
|
||||
const { result } = renderHook(() => useAppSettings("proj_123"));
|
||||
|
||||
// Initial state from first mock
|
||||
await waitFor(() => {
|
||||
expect(result.current.showQuickChatFAB).toBe(false);
|
||||
});
|
||||
|
||||
// Change mock to return different value
|
||||
mockFetchSettings.mockResolvedValueOnce({
|
||||
autoMerge: false,
|
||||
globalPause: true,
|
||||
enginePaused: false,
|
||||
githubTokenConfigured: true,
|
||||
taskStuckTimeoutMs: 600000,
|
||||
showQuickChatFAB: true,
|
||||
} as never);
|
||||
|
||||
// Call refresh
|
||||
await act(async () => {
|
||||
await result.current.refresh();
|
||||
});
|
||||
|
||||
// Verify state was updated
|
||||
await waitFor(() => {
|
||||
expect(result.current.showQuickChatFAB).toBe(true);
|
||||
});
|
||||
|
||||
// Verify fetchSettings was called again with correct projectId
|
||||
expect(mockFetchSettings).toHaveBeenCalledWith("proj_123");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,8 @@ export interface UseAppSettingsResult {
|
||||
toggleGlobalPause: () => Promise<void>;
|
||||
toggleEnginePause: () => Promise<void>;
|
||||
toggleShowQuickChatFAB: () => Promise<void>;
|
||||
/** Re-fetches settings from the backend to pick up changes made externally (e.g., by SettingsModal). */
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,30 +34,36 @@ export function useAppSettings(projectId?: string): UseAppSettingsResult {
|
||||
const [showQuickChatFAB, setShowQuickChatFAB] = useState(true);
|
||||
const [githubTokenConfigured, setGithubTokenConfigured] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchConfig(projectId)
|
||||
.then((cfg) => {
|
||||
setMaxConcurrent(cfg.maxConcurrent);
|
||||
setRootDir(cfg.rootDir);
|
||||
})
|
||||
.catch(() => {
|
||||
// Keep defaults on fetch failure.
|
||||
});
|
||||
/**
|
||||
* Fetches config and settings from the backend and updates local state.
|
||||
* Shared between the mount-time useEffect and the refresh() function.
|
||||
*/
|
||||
const refresh = useCallback(async () => {
|
||||
try {
|
||||
const cfg = await fetchConfig(projectId);
|
||||
setMaxConcurrent(cfg.maxConcurrent);
|
||||
setRootDir(cfg.rootDir);
|
||||
} catch {
|
||||
// Keep current state on fetch failure.
|
||||
}
|
||||
|
||||
fetchSettings(projectId)
|
||||
.then((settings) => {
|
||||
setAutoMerge(Boolean(settings.autoMerge));
|
||||
setGlobalPaused(Boolean(settings.globalPause));
|
||||
setEnginePaused(Boolean(settings.enginePaused));
|
||||
setGithubTokenConfigured(Boolean(settings.githubTokenConfigured));
|
||||
setTaskStuckTimeoutMs(settings.taskStuckTimeoutMs);
|
||||
setShowQuickChatFAB(settings.showQuickChatFAB !== false);
|
||||
})
|
||||
.catch(() => {
|
||||
// Keep defaults on fetch failure.
|
||||
});
|
||||
try {
|
||||
const settings = await fetchSettings(projectId);
|
||||
setAutoMerge(Boolean(settings.autoMerge));
|
||||
setGlobalPaused(Boolean(settings.globalPause));
|
||||
setEnginePaused(Boolean(settings.enginePaused));
|
||||
setGithubTokenConfigured(Boolean(settings.githubTokenConfigured));
|
||||
setTaskStuckTimeoutMs(settings.taskStuckTimeoutMs);
|
||||
setShowQuickChatFAB(settings.showQuickChatFAB !== false);
|
||||
} catch {
|
||||
// Keep current state on fetch failure.
|
||||
}
|
||||
}, [projectId]);
|
||||
|
||||
useEffect(() => {
|
||||
void refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const toggleAutoMerge = useCallback(async () => {
|
||||
const next = !autoMerge;
|
||||
setAutoMerge(next);
|
||||
@@ -113,5 +121,6 @@ export function useAppSettings(projectId?: string): UseAppSettingsResult {
|
||||
toggleGlobalPause,
|
||||
toggleEnginePause,
|
||||
toggleShowQuickChatFAB,
|
||||
refresh,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user