feat(FN-3079): add plugin dashboard views and dependency graph plugin
- Add plugin dashboard view registration and hosting across core, dashboard routes, and plugin SDK exports - Integrate plugin-provided views into app navigation, mobile/header UI, and view state hooks with coverage - Add fusion-plugin-dependency-graph example plugin with persisted storage, dashboard view UI, and manifest wiring - Update plugin authoring and architecture docs for dashboard view extension points - Add a changeset for @runfusion/fusion covering plugin dashboard view support Fusion-Task-Id: FN-3079
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { usePluginDashboardViews, __test_clearDashboardViewsCache } from "../usePluginDashboardViews";
|
||||
import * as api from "../../api";
|
||||
|
||||
vi.mock("../../api", () => ({
|
||||
fetchPluginDashboardViews: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockFetch = vi.mocked(api.fetchPluginDashboardViews);
|
||||
|
||||
describe("usePluginDashboardViews", () => {
|
||||
beforeEach(() => {
|
||||
__test_clearDashboardViewsCache();
|
||||
mockFetch.mockReset();
|
||||
});
|
||||
|
||||
it("fetches and returns dashboard views", async () => {
|
||||
mockFetch.mockResolvedValueOnce([
|
||||
{ pluginId: "dep", view: { viewId: "graph", label: "Graph", componentPath: "./Graph.js" } },
|
||||
]);
|
||||
|
||||
const { result } = renderHook(() => usePluginDashboardViews());
|
||||
await waitFor(() => expect(result.current.loading).toBe(false));
|
||||
expect(result.current.views).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("uses project-scoped cache keys", async () => {
|
||||
mockFetch.mockResolvedValueOnce([{ pluginId: "a", view: { viewId: "x", label: "X", componentPath: "./x.js" } }]);
|
||||
const first = renderHook(() => usePluginDashboardViews("project-a"));
|
||||
await waitFor(() => expect(first.result.current.loading).toBe(false));
|
||||
|
||||
mockFetch.mockClear();
|
||||
renderHook(() => usePluginDashboardViews("project-a"));
|
||||
expect(mockFetch).not.toHaveBeenCalled();
|
||||
|
||||
mockFetch.mockResolvedValueOnce([{ pluginId: "b", view: { viewId: "y", label: "Y", componentPath: "./y.js" } }]);
|
||||
const second = renderHook(() => usePluginDashboardViews("project-b"));
|
||||
await waitFor(() => expect(second.result.current.loading).toBe(false));
|
||||
expect(mockFetch).toHaveBeenCalledWith("project-b");
|
||||
});
|
||||
});
|
||||
@@ -275,6 +275,28 @@ describe("useViewState", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("restores and persists plugin task views using the canonical composite key", async () => {
|
||||
localStorage.setItem("kb:proj_123:kb-dashboard-task-view", "plugin:fusion-plugin-dependency-graph:graph");
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useViewState(
|
||||
createOptions({
|
||||
currentProject: PROJECT,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.taskView).toBe("plugin:fusion-plugin-dependency-graph:graph");
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
result.current.setTaskView("plugin:fusion-plugin-dependency-graph:graph");
|
||||
});
|
||||
|
||||
expect(localStorage.getItem("kb:proj_123:kb-dashboard-task-view")).toBe("plugin:fusion-plugin-dependency-graph:graph");
|
||||
});
|
||||
|
||||
it("restores legacy views (board/list/agents/missions/chat) from scoped storage", async () => {
|
||||
const legacyViews = ["board", "list", "agents", "missions", "chat"] as const;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user