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:
@@ -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.
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user