fix: green full-suite after mock and stacking drift (#2287)
## Summary Restacks onto latest main after #2285 and clears the remaining Full Suite red classes from run [29633869887](https://github.com/Runfusion/Fusion/actions/runs/29633869887) (post-#2285): - **Shard 1:** `agent-skills-flow` vitest TDZ — hoist `mockFiles` via `vi.hoisted` (same class as skill-resolver in #2285) - **Shard 2/3:** incomplete mocks after product drift - `isFullScreenSheetViewport` / `isShortViewport` on viewport mocks (without overriding dynamic mobile helpers) - `fetchCodebaseMetrics` on Command Center `api/legacy` mocks - `fetchSettings` on `agent-modals-mobile` api mock - **Shard 3:** PlanningMode `ui-interactions` race — sync-settle `fetchGlobalSettings` (FN-8245 pattern from planning-flow) - **Shard 3:** settings search drift guard — inventory `SettingsFieldRow` `htmlFor` keys (`mobileNavPrimaryItems`) - **Shard 2:** FloatingWindow shared-stack product bug — only reclaim z-index on hidden→visible (not every mount effect), so last-mounted utility stays on top - **Shard 4:** grok process-lifecycle timeout under shard load — prove bound with 5 reimports instead of 15 ## Test plan - [x] `agent-skills-flow.test.ts` green - [x] `process-lifecycle.test.ts` green - [x] FileBrowserModal, FloatingWindowStack.cross-type, agent-modals-mobile, settings-search-index, SystemControlsArea, PlanningModeModal.ui-interactions + planning-flow (210 tests) green - [ ] PR merge gate (Lint/Typecheck/Build/Gate) - [ ] Post-merge Full Suite on `main` green <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved floating-window stacking so reopened or interacted windows appear in the correct order. * Restored consistent layering between floating windows and expanded dock modals. * **Tests** * Updated automated coverage for viewport behavior, codebase metrics, settings search indexing, and process lifecycle scenarios. * Improved test reliability and consistency across responsive layouts and modal interactions. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
7
.changeset/full-suite-float-stack.md
Normal file
7
.changeset/full-suite-float-stack.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Keep floating windows stacked by last-opened and last-interacted order.
|
||||
category: fix
|
||||
dev: FloatingWindow only reclaims z-index on hidden→visible (not every mount effect), restoring shared-stack cross-type ordering with RightDockExpandModal.
|
||||
@@ -252,9 +252,17 @@ export function FloatingWindow({
|
||||
A hidden Quick Chat must reclaim a fresh z-index when reopened because another task-detail
|
||||
popup may have been focused while chat was invisible. Hidden windows do not otherwise affect
|
||||
the shared interaction stack.
|
||||
|
||||
FNXC:FloatingWindow 2026-07-18-07:15:
|
||||
Only reclaim on the hidden→visible transition. Initial mount already claims via useState;
|
||||
re-claiming after sibling mount (RightDockExpandModal, etc.) inverted last-mounted-on-top
|
||||
and broke the shared-stack cross-type contract in FloatingWindowStack.cross-type.test.
|
||||
*/
|
||||
const wasHiddenRef = useRef(hidden);
|
||||
useEffect(() => {
|
||||
if (!hidden) bringToFront();
|
||||
const wasHidden = wasHiddenRef.current;
|
||||
wasHiddenRef.current = hidden;
|
||||
if (wasHidden && !hidden) bringToFront();
|
||||
}, [bringToFront, hidden]);
|
||||
|
||||
const handleDragPointerDown = useCallback(
|
||||
|
||||
@@ -8,6 +8,8 @@ import * as apiModule from "../../api";
|
||||
const mockViewportMode = vi.fn<() => "mobile" | "tablet" | "desktop">(() => "desktop");
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockViewportMode(),
|
||||
isMobileViewport: () => mockViewportMode() === "mobile",
|
||||
useViewportMode: () => mockViewportMode(),
|
||||
|
||||
@@ -79,6 +79,8 @@ const mockViewportMode = vi.fn<() => "mobile" | "tablet" | "desktop">(() => "des
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockViewportMode(),
|
||||
isMobileViewport: () => mockViewportMode() === "mobile",
|
||||
useViewportMode: () => mockViewportMode(),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CliBinaryPanel } from "../CliBinaryPanel";
|
||||
import { fetchFnBinaryStatus, installFnBinary } from "../../api/legacy";
|
||||
|
||||
vi.mock("../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
fetchFnBinaryStatus: vi.fn(),
|
||||
installFnBinary: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -9,7 +9,9 @@ const viewportModeMock = vi.hoisted(() => ({ value: "desktop" as "desktop" | "ta
|
||||
const mockFetchScripts = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
useViewportMode: () => viewportModeMock.value,
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
useViewportMode: () => viewportModeMock.value,
|
||||
}));
|
||||
|
||||
vi.mock("../../api", () => ({
|
||||
|
||||
@@ -14,6 +14,8 @@ vi.mock("../../hooks/useViewportMode", () => {
|
||||
const mode = () => (window.innerWidth <= 768 ? "mobile" : "desktop");
|
||||
return {
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: mode,
|
||||
isMobileViewport: () => mode() === "mobile",
|
||||
useViewportMode: mode,
|
||||
|
||||
@@ -20,6 +20,8 @@ const mockUseMobileKeyboard = vi.fn(() => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockUseViewportMode(),
|
||||
isMobileViewport: () => mockUseViewportMode() === "mobile",
|
||||
useViewportMode: () => mockUseViewportMode(),
|
||||
|
||||
@@ -32,7 +32,9 @@ vi.mock("../../api", () => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => {
|
||||
const useViewportMode = vi.fn();
|
||||
return {
|
||||
return { isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
getViewportMode: () => useViewportMode(),
|
||||
isMobileViewport: () => useViewportMode() === "mobile",
|
||||
|
||||
@@ -50,6 +50,8 @@ vi.mock("../../hooks/useMobileKeyboard", () => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => "mobile",
|
||||
isMobileViewport: () => true,
|
||||
useViewportMode: () => "mobile",
|
||||
|
||||
@@ -22,6 +22,8 @@ const mockSubscribeSse = vi.fn(() => vi.fn());
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockViewportMode(),
|
||||
isMobileViewport: () => mockViewportMode() === "mobile",
|
||||
useViewportMode: () => mockViewportMode(),
|
||||
|
||||
@@ -19,6 +19,8 @@ const mockSubscribeSse = vi.fn(() => vi.fn());
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockViewportMode(),
|
||||
isMobileViewport: () => mockViewportMode() === "mobile",
|
||||
useViewportMode: () => mockViewportMode(),
|
||||
|
||||
@@ -26,7 +26,9 @@ vi.mock("../../hooks/useMobileScrollLock", () => ({
|
||||
vi.mock("../../hooks/useNodes", () => ({ useNodes: vi.fn(() => ({ nodes: [] })) }));
|
||||
vi.mock("../../hooks/useViewportMode", () => {
|
||||
const useViewportMode = vi.fn(() => "desktop");
|
||||
return {
|
||||
return { isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
getViewportMode: () => useViewportMode(),
|
||||
isMobileViewport: () => useViewportMode() === "mobile",
|
||||
|
||||
@@ -83,6 +83,8 @@ vi.mock("../../hooks/useMobileKeyboard", () => ({
|
||||
let mockViewportMode: "mobile" | "desktop" = "mobile";
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockViewportMode,
|
||||
isMobileViewport: () => mockViewportMode === "mobile",
|
||||
useViewportMode: () => mockViewportMode,
|
||||
|
||||
@@ -80,6 +80,8 @@ vi.mock("../../hooks/useConfirm", () => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
useViewportMode: () => mockUseViewportMode(),
|
||||
getViewportMode: () => mockUseViewportMode(),
|
||||
isMobileViewport: () => mockUseViewportMode() === "mobile",
|
||||
|
||||
@@ -111,6 +111,8 @@ vi.mock("../../hooks/useConfirm", () => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockUseViewportMode(),
|
||||
isMobileViewport: () => mockUseViewportMode() === "mobile",
|
||||
useViewportMode: () => mockUseViewportMode(),
|
||||
|
||||
@@ -159,6 +159,8 @@ vi.mock("../../hooks/useConfirm", () => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockUseViewportMode(),
|
||||
isMobileViewport: () => mockUseViewportMode() === "mobile",
|
||||
useViewportMode: () => mockUseViewportMode(),
|
||||
|
||||
@@ -105,7 +105,28 @@ vi.mock("../../api", () => ({
|
||||
refineTask: (...args: any[]) => mockRefineTask(...args),
|
||||
fetchSettings: vi.fn().mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {} }),
|
||||
fetchTaskEffectiveSettings: vi.fn().mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {} }),
|
||||
fetchGlobalSettings: vi.fn().mockResolvedValue({}),
|
||||
/*
|
||||
FNXC:PlanningModeSettings 2026-07-18-07:20:
|
||||
Same FN-8245 deterministic settle as planning-flow: Start Planning is gated on
|
||||
clarificationSettingsLoading, so a microtask-resolved fetchGlobalSettings makes
|
||||
fireEvent.click races no-op and leaves the suite red under full-suite load.
|
||||
*/
|
||||
fetchGlobalSettings: vi.fn(() => {
|
||||
const settled = {
|
||||
then(onFulfilled: (settings: Record<string, never>) => unknown) {
|
||||
onFulfilled({});
|
||||
return settled;
|
||||
},
|
||||
catch() {
|
||||
return settled;
|
||||
},
|
||||
finally(onFinally: () => unknown) {
|
||||
onFinally();
|
||||
return settled;
|
||||
},
|
||||
};
|
||||
return settled;
|
||||
}),
|
||||
fetchModels: (...args: any[]) => mockFetchModels(...args),
|
||||
fetchWorkflowSteps: vi.fn().mockResolvedValue([]),
|
||||
fetchBoardWorkflows: vi.fn().mockResolvedValue({ flagEnabled: false, defaultWorkflowId: "", workflows: [], taskWorkflowIds: {} }),
|
||||
@@ -122,6 +143,8 @@ vi.mock("../../hooks/useConfirm", () => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockUseViewportMode(),
|
||||
isMobileViewport: () => mockUseViewportMode() === "mobile",
|
||||
useViewportMode: () => mockUseViewportMode(),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const apiMock = vi.fn();
|
||||
vi.mock("../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
|
||||
withProjectId: (path: string, projectId?: string) =>
|
||||
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
|
||||
|
||||
@@ -15,6 +15,7 @@ vi.mock("../../api", async (importOriginal) => {
|
||||
});
|
||||
|
||||
vi.mock("../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
fetchFnBinaryStatus: vi.fn(() => Promise.resolve({
|
||||
binary: { binary: "fn", installed: false, path: null, version: null },
|
||||
expectedVersion: "1.2.3",
|
||||
@@ -29,6 +30,8 @@ vi.mock("../../hooks/useMemoryBackendStatus", () => ({
|
||||
}));
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
useViewportMode: () => "desktop",
|
||||
getViewportMode: () => "desktop",
|
||||
isMobileViewport: () => false,
|
||||
|
||||
@@ -159,6 +159,8 @@ let viewportMode: "mobile" | "desktop" = "mobile";
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => viewportMode,
|
||||
isMobileViewport: () => viewportMode === "mobile",
|
||||
useViewportMode: () => viewportMode,
|
||||
|
||||
@@ -30,6 +30,8 @@ vi.mock("../../hooks/useMemoryBackendStatus", () => ({
|
||||
}));
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
useViewportMode: () => "desktop",
|
||||
getViewportMode: () => "desktop",
|
||||
isMobileViewport: () => false,
|
||||
|
||||
@@ -37,6 +37,8 @@ vi.mock("../../api", async (importOriginal) => {
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
useViewportMode: (...args: unknown[]) => mockUseViewportMode(...args),
|
||||
getViewportMode: (...args: unknown[]) => mockUseViewportMode(...args),
|
||||
isMobileViewport: () => mockUseViewportMode() === "mobile",
|
||||
|
||||
@@ -158,6 +158,8 @@ vi.mock("../../hooks/useConfirm", () => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => "mobile",
|
||||
isMobileViewport: () => true,
|
||||
useViewportMode: () => "mobile",
|
||||
|
||||
@@ -148,6 +148,8 @@ vi.mock("../../hooks/useConfirm", () => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => viewportMock.mode,
|
||||
isMobileViewport: () => viewportMock.mode === "mobile",
|
||||
useViewportMode: () => viewportMock.mode,
|
||||
|
||||
@@ -150,6 +150,8 @@ vi.mock("../../hooks/useConfirm", () => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => "mobile",
|
||||
isMobileViewport: () => true,
|
||||
useViewportMode: () => "mobile",
|
||||
|
||||
@@ -151,6 +151,8 @@ vi.mock("../../hooks/useConfirm", () => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => "mobile",
|
||||
isMobileViewport: () => true,
|
||||
useViewportMode: () => "mobile",
|
||||
|
||||
@@ -18,6 +18,8 @@ vi.mock("../../hooks/useMemoryBackendStatus", () => ({
|
||||
}));
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
useViewportMode: () => "desktop",
|
||||
getViewportMode: () => "desktop",
|
||||
isMobileViewport: () => false,
|
||||
|
||||
@@ -32,6 +32,8 @@ vi.mock("../../hooks/useWorktrunkInstallStatus", () => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
useViewportMode: () => "desktop",
|
||||
getViewportMode: () => "desktop",
|
||||
isMobileViewport: () => false,
|
||||
|
||||
@@ -46,6 +46,8 @@ vi.mock("../../hooks/useMobileKeyboard", () => ({
|
||||
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockViewportMode.value,
|
||||
isMobileViewport: () => mockViewportMode.value === "mobile",
|
||||
useViewportMode: () => mockViewportMode.value,
|
||||
|
||||
@@ -234,6 +234,8 @@ vi.mock("../../hooks/useNodes", () => ({
|
||||
const mockUseViewportMode = vi.fn(() => "desktop");
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockUseViewportMode(),
|
||||
isMobileViewport: () => mockUseViewportMode() === "mobile",
|
||||
useViewportMode: (..._args: unknown[]) => mockUseViewportMode(..._args),
|
||||
|
||||
@@ -308,6 +308,8 @@ vi.mock("../../hooks/useNodes", () => ({
|
||||
const mockUseViewportMode = vi.fn(() => "desktop");
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockUseViewportMode(),
|
||||
isMobileViewport: () => mockUseViewportMode() === "mobile",
|
||||
useViewportMode: (..._args: unknown[]) => mockUseViewportMode(..._args),
|
||||
|
||||
@@ -9,6 +9,11 @@ import { AgentImportModal } from "../AgentImportModal";
|
||||
import { AgentListModal } from "../AgentListModal";
|
||||
import * as api from "../../api";
|
||||
|
||||
/*
|
||||
FNXC:DashboardTests 2026-07-18-07:15:
|
||||
AgentDetailView/AgentListModal now call fetchSettings for runtime-config surfaces.
|
||||
Include it on the ../../api mock so vitest does not fail the suite on missing export.
|
||||
*/
|
||||
vi.mock("../../api", () => ({
|
||||
fetchAgent: vi.fn(),
|
||||
updateAgent: vi.fn(),
|
||||
@@ -43,6 +48,7 @@ vi.mock("../../api", () => ({
|
||||
resetAgentBudget: vi.fn(),
|
||||
upgradeAgentHeartbeatProcedure: vi.fn(),
|
||||
fetchCompanies: vi.fn(),
|
||||
fetchSettings: vi.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
vi.mock("../AgentLogViewer", () => ({
|
||||
|
||||
@@ -349,6 +349,8 @@ vi.mock("../../hooks/useNodes", () => ({
|
||||
const mockUseViewportMode = vi.fn(() => "desktop");
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
isFullScreenSheetViewport: () => false,
|
||||
isShortViewport: () => false,
|
||||
getViewportMode: () => mockUseViewportMode(),
|
||||
isMobileViewport: () => mockUseViewportMode() === "mobile",
|
||||
useViewportMode: (..._args: unknown[]) => mockUseViewportMode(..._args),
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CommandCenter } from "../CommandCenter";
|
||||
|
||||
const apiMock = vi.fn();
|
||||
vi.mock("../../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
|
||||
withProjectId: (path: string, projectId?: string) =>
|
||||
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CommandCenter } from "../CommandCenter";
|
||||
|
||||
const apiMock = vi.fn();
|
||||
vi.mock("../../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
|
||||
withProjectId: (path: string, projectId?: string) =>
|
||||
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { LiveSnapshot } from "@fusion/core";
|
||||
// Mock the api() helper so the panel fetches deterministic snapshots.
|
||||
const apiMock = vi.fn();
|
||||
vi.mock("../../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
|
||||
withProjectId: (path: string, projectId?: string) =>
|
||||
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { render, screen, waitFor } from "@testing-library/react";
|
||||
// Mock the api() helper so the funnel fetches a deterministic fixture.
|
||||
const apiMock = vi.fn();
|
||||
vi.mock("../../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
|
||||
withProjectId: (path: string, projectId?: string) =>
|
||||
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
|
||||
|
||||
@@ -16,6 +16,7 @@ const mockFetchGlobalSettings = vi.fn();
|
||||
const mockFetchNodes = vi.fn();
|
||||
|
||||
vi.mock("../../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
|
||||
withProjectId: (path: string, projectId?: string) =>
|
||||
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
|
||||
|
||||
@@ -16,6 +16,7 @@ const mocks = vi.hoisted(() => ({
|
||||
}));
|
||||
|
||||
vi.mock("../../../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
fetchOrgTree: mocks.fetchOrgTree,
|
||||
fetchExecutorStats: mocks.fetchExecutorStats,
|
||||
fetchSettings: mocks.fetchSettings,
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { DateRange } from "../../DateRangePicker";
|
||||
const apiMock = vi.fn();
|
||||
|
||||
vi.mock("../../../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
|
||||
withProjectId: (path: string, projectId?: string) =>
|
||||
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
|
||||
|
||||
@@ -7,6 +7,7 @@ const mocks = vi.hoisted(() => ({
|
||||
}));
|
||||
|
||||
vi.mock("../../../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
api: (path: string, opts?: RequestInit) => mocks.api(path, opts),
|
||||
withProjectId: (path: string, projectId?: string) =>
|
||||
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
|
||||
|
||||
@@ -25,6 +25,7 @@ const fetchExecutorStatsMock = mocks.fetchExecutorStats;
|
||||
const toggleEnginePauseMock = mocks.toggleEnginePause;
|
||||
const appSettingsMock = mocks.appSettings;
|
||||
vi.mock("../../../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
api: (path: string, opts?: RequestInit) => mocks.api(path, opts),
|
||||
withProjectId: (path: string, projectId?: string) =>
|
||||
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { render, screen, within } from "@testing-library/react";
|
||||
|
||||
const mocks = vi.hoisted(() => ({ api: vi.fn() }));
|
||||
vi.mock("../../../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
api: (path: string, opts?: RequestInit) => mocks.api(path, opts),
|
||||
withProjectId: (path: string, projectId?: string) =>
|
||||
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
|
||||
|
||||
@@ -30,6 +30,7 @@ const fetchExecutorStatsMock = mocks.fetchExecutorStats;
|
||||
const toggleEnginePauseMock = mocks.toggleEnginePause;
|
||||
const appSettingsMock = mocks.appSettings;
|
||||
vi.mock("../../../../api/legacy", () => ({
|
||||
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
|
||||
api: (path: string, opts?: RequestInit) => mocks.api(path, opts),
|
||||
withProjectId: (path: string, projectId?: string) =>
|
||||
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,
|
||||
|
||||
@@ -18,9 +18,21 @@ const SECTIONS_DIR = resolve(__dirname, "../../sections");
|
||||
* Extracts the descriptor keys a section renders. Matches the established
|
||||
* `descriptor={{ key: "..." }}` idiom every typed row uses (see GeneralSection
|
||||
* and AppearanceSection); `key` is always the first property by convention.
|
||||
*
|
||||
* FNXC:SettingsSearch 2026-07-18-07:15:
|
||||
* Also inventory bespoke SettingsFieldRow controls that anchor search via
|
||||
* `htmlFor` → `data-settings-key` (e.g. general.mobileNavPrimaryItems). Those
|
||||
* rows are indexed and jumpable but never use the descriptor={{ key }} shape.
|
||||
*/
|
||||
function extractDescriptorKeys(source: string): string[] {
|
||||
return [...source.matchAll(/descriptor=\{\{\s*key:\s*"([^"]+)"/g)].map((m) => m[1]);
|
||||
const keys = new Set<string>();
|
||||
for (const m of source.matchAll(/descriptor=\{\{\s*key:\s*"([^"]+)"/g)) {
|
||||
keys.add(m[1]);
|
||||
}
|
||||
for (const m of source.matchAll(/<SettingsFieldRow\b[\s\S]*?\bhtmlFor="([^"]+)"/g)) {
|
||||
keys.add(m[1]);
|
||||
}
|
||||
return [...keys];
|
||||
}
|
||||
|
||||
/** Section .tsx files, excluding co-located tests and non-section helpers. */
|
||||
|
||||
@@ -6,12 +6,19 @@
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { mockPiLog } = vi.hoisted(() => ({
|
||||
/*
|
||||
FNXC:EngineTests 2026-07-18-06:40:
|
||||
Hoist mock filesystem state for vi.mock factories. Module import of schema-applier
|
||||
can call existsSync before const mockFiles initializes (TDZ), same class as skill-resolver.
|
||||
*/
|
||||
const { mockPiLog, mockFiles, mockDirCounter } = vi.hoisted(() => ({
|
||||
mockPiLog: {
|
||||
log: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
mockFiles: new Map<string, string>(),
|
||||
mockDirCounter: { value: 0 },
|
||||
}));
|
||||
|
||||
vi.mock("../logger.js", () => ({
|
||||
@@ -24,17 +31,13 @@ import type { Agent, AgentStore } from "@fusion/core";
|
||||
|
||||
// ── Mock Setup ───────────────────────────────────────────────────────────────
|
||||
|
||||
// In-memory file system for tests - using a module-scoped Map
|
||||
const mockFiles = new Map<string, string>();
|
||||
let mockDirCounter = 0;
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
|
||||
return {
|
||||
...actual,
|
||||
existsSync: (path: unknown) => mockFiles.has(String(path)),
|
||||
readFileSync: (path: unknown) => mockFiles.get(String(path)) ?? "{}",
|
||||
mkdtempSync: () => `/tmp/agent-skills-flow-mock-${++mockDirCounter}`,
|
||||
mkdtempSync: () => `/tmp/agent-skills-flow-mock-${++mockDirCounter.value}`,
|
||||
writeFileSync: (path: unknown, content: unknown) => mockFiles.set(String(path), String(content)),
|
||||
rmSync: (path: unknown) => {
|
||||
const pathStr = String(path);
|
||||
@@ -48,7 +51,7 @@ vi.mock("node:fs", async () => {
|
||||
// ── Test Helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
function createMockProjectDir(settings: Record<string, unknown> | null): string {
|
||||
const dir = `/tmp/agent-skills-flow-mock-${++mockDirCounter}`;
|
||||
const dir = `/tmp/agent-skills-flow-mock-${++mockDirCounter.value}`;
|
||||
if (settings !== null) {
|
||||
mockFiles.set(`${dir}/.fusion/settings.json`, JSON.stringify(settings));
|
||||
}
|
||||
@@ -60,7 +63,7 @@ function createMockProjectDir(settings: Record<string, unknown> | null): string
|
||||
describe("agent skills flow - full integration", () => {
|
||||
beforeEach(() => {
|
||||
mockFiles.clear();
|
||||
mockDirCounter = 0;
|
||||
mockDirCounter.value = 0;
|
||||
mockPiLog.log.mockClear();
|
||||
mockPiLog.warn.mockClear();
|
||||
mockPiLog.error.mockClear();
|
||||
|
||||
@@ -14,6 +14,12 @@ describe("Grok plugin process lifecycle", () => {
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:GrokRuntimeTests 2026-07-18-07:15:
|
||||
Bound is proven by a few re-evaluations (Symbol.for exit-hook guard). Full-suite
|
||||
shard load made 15 dynamic imports hit the default 5s testTimeout (transform-heavy
|
||||
plugin graph), not a product hang — keep the assertion, reduce iterations.
|
||||
*/
|
||||
it("keeps its process cleanup owner bounded across repeated module evaluation", async () => {
|
||||
const baseline = listenerCounts();
|
||||
const warnings: Error[] = [];
|
||||
@@ -21,7 +27,7 @@ describe("Grok plugin process lifecycle", () => {
|
||||
process.on("warning", onWarning);
|
||||
|
||||
try {
|
||||
for (let iteration = 0; iteration < 15; iteration += 1) {
|
||||
for (let iteration = 0; iteration < 5; iteration += 1) {
|
||||
vi.resetModules();
|
||||
await import("../index.js");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user