feat(FN-3916): fix bundled Dependency Graph view load path

Fixed the bundled Dependency Graph view load path in the dashboard app, adding regression tests across the App and plugin registration test suites, and documenting the bundled loading contract in the plugin README.

Fusion-Task-Id: FN-3916
This commit is contained in:
Fusion
2026-05-10 01:24:20 -07:00
committed by gsxdsm
parent 0810827d36
commit 4a20dc4ab2
4 changed files with 55 additions and 5 deletions

View File

@@ -50,7 +50,7 @@ import { useViewState, type TaskView } from "./hooks/useViewState";
import { useNavigationHistory } from "./hooks/useNavigationHistory";
import { usePluginDashboardViews } from "./hooks/usePluginDashboardViews";
import { PluginDashboardViewHost } from "./plugins/PluginDashboardViewHost";
import { isPluginViewId } from "./plugins/pluginViewRegistry";
import { isPluginViewId, isPluginViewRegistered } from "./plugins/pluginViewRegistry";
import { registerBundledPluginViews } from "./plugins/registerBundledPluginViews";
import { useProjectActions } from "./hooks/useProjectActions";
import { useTaskHandlers } from "./hooks/useTaskHandlers";
@@ -331,10 +331,17 @@ function AppInner() {
const { views: pluginDashboardViews } = usePluginDashboardViews(currentProject?.id);
const graphPluginTaskView = useMemo(() => {
// Prefer API response for the graph view (supports dynamic plugin discovery)
const graphView = pluginDashboardViews.find(
(entry) => entry.pluginId === "fusion-plugin-dependency-graph" && entry.view.viewId === "graph",
);
return graphView ? (`plugin:${graphView.pluginId}:${graphView.view.viewId}` as const) : null;
if (graphView) return `plugin:${graphView.pluginId}:${graphView.view.viewId}` as const;
// Fall back to bundled static registration so the graph view works even when
// the plugin is not installed/loaded through the API (e.g. fresh DB).
if (isPluginViewRegistered("fusion-plugin-dependency-graph", "graph")) {
return `plugin:fusion-plugin-dependency-graph:graph` as const;
}
return null;
}, [pluginDashboardViews]);
// History-aware view change handler — pushes nav entry on back-navigation stack.

View File

@@ -2034,6 +2034,24 @@ describe("App view switching", () => {
localStorage.removeItem("kb-dashboard-view-mode");
});
// FN-3916 regression: graph view must render via bundled static registration
// even when the API reports no plugin dashboard views (e.g. fresh DB).
it("renders graph view from bundled static registration when API returns no views", async () => {
localStorage.setItem("kb-dashboard-view-mode", "project");
localStorage.setItem(taskViewStorageKey(), "graph");
// API returns empty — plugin not installed/loaded
(fetchPluginDashboardViews as ReturnType<typeof vi.fn>).mockResolvedValueOnce([]);
render(<App />);
await waitFor(() => {
expect(screen.getByTestId("dependency-graph")).toBeInTheDocument();
});
localStorage.removeItem(taskViewStorageKey());
localStorage.removeItem("kb-dashboard-view-mode");
});
it("restores board and plugin routes when persisted taskView changes across remounts", async () => {
localStorage.setItem("kb-dashboard-view-mode", "project");

View File

@@ -1,16 +1,20 @@
import { describe, expect, it, beforeEach, vi } from "vitest";
import { getPluginViewComponent, __test_clearPluginViewRegistry } from "../pluginViewRegistry";
import { createElement } from "react";
import { getPluginViewComponent, isPluginViewRegistered, __test_clearPluginViewRegistry } from "../pluginViewRegistry";
import {
__test_resetBundledPluginViewRegistration,
registerBundledPluginViews,
} from "../registerBundledPluginViews";
const MockDependencyGraphDashboardView = () => createElement("div", { "data-testid": "dep-graph-view" });
const MockRoadmapDashboardView = () => createElement("div", { "data-testid": "roadmap-view" });
vi.mock("@fusion-plugin-examples/dependency-graph/dashboard-view", () => ({
DependencyGraphDashboardView: () => null,
DependencyGraphDashboardView: (...args: unknown[]) => MockDependencyGraphDashboardView(...args),
}));
vi.mock("@fusion-plugin-examples/roadmap/dashboard-view", () => ({
RoadmapDashboardView: () => null,
RoadmapDashboardView: (...args: unknown[]) => MockRoadmapDashboardView(...args),
}));
describe("registerBundledPluginViews", () => {
@@ -33,4 +37,15 @@ describe("registerBundledPluginViews", () => {
expect(() => registerBundledPluginViews()).not.toThrow();
expect(getPluginViewComponent("fusion-plugin-dependency-graph", "graph")).toBe(firstGraph);
});
// FN-3916 regression: verifies the registry reports the graph view as registered
// so App.tsx can fall back to bundled static registration when API has no views.
it("reports dependency graph as registered via isPluginViewRegistered", () => {
registerBundledPluginViews();
expect(isPluginViewRegistered("fusion-plugin-dependency-graph", "graph")).toBe(true);
expect(isPluginViewRegistered("roadmap-planner", "roadmaps")).toBe(true);
// Unknown plugin/view should not be registered
expect(isPluginViewRegistered("unknown-plugin", "unknown")).toBe(false);
});
});