feat(FN-3091): add plugin view remount routing test coverage
Adds test coverage for plugin view remount routing behavior in the App component. Fusion-Task-Id: FN-3091
This commit is contained in:
@@ -1730,6 +1730,44 @@ export default plugin;
|
||||
"views-b:timeline",
|
||||
]);
|
||||
expect(loader.getPluginUiSlots()).toHaveLength(1);
|
||||
expect(loader.getPluginTools()).toEqual([]);
|
||||
expect(loader.getPluginRoutes()).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns pluginId and complete view payload for each dashboard view entry", async () => {
|
||||
await pluginStore.init();
|
||||
const loader = new PluginLoader({ pluginStore, taskStore: mockTaskStore });
|
||||
(loader as any).plugins.set("views-shape", {
|
||||
manifest: makeManifest({ id: "views-shape" }),
|
||||
state: "started",
|
||||
hooks: {},
|
||||
dashboardViews: [
|
||||
{
|
||||
viewId: "graph",
|
||||
label: "Graph",
|
||||
componentPath: "./graph.js",
|
||||
icon: "Network",
|
||||
placement: "more",
|
||||
description: "Task dependency graph",
|
||||
order: 40,
|
||||
},
|
||||
],
|
||||
} as FusionPlugin);
|
||||
|
||||
expect(loader.getPluginDashboardViews()).toEqual([
|
||||
{
|
||||
pluginId: "views-shape",
|
||||
view: {
|
||||
viewId: "graph",
|
||||
label: "Graph",
|
||||
componentPath: "./graph.js",
|
||||
icon: "Network",
|
||||
placement: "more",
|
||||
description: "Task dependency graph",
|
||||
order: 40,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1784,6 +1784,48 @@ describe("App view switching", () => {
|
||||
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");
|
||||
|
||||
localStorage.setItem(taskViewStorageKey(), "plugin:fusion-plugin-dependency-graph:graph");
|
||||
(fetchPluginDashboardViews as ReturnType<typeof vi.fn>).mockResolvedValueOnce([
|
||||
{
|
||||
pluginId: "fusion-plugin-dependency-graph",
|
||||
view: { viewId: "graph", label: "Graph", componentPath: "./GraphView", placement: "more" },
|
||||
},
|
||||
]);
|
||||
|
||||
const first = render(<App />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Plugin view unavailable")).toBeInTheDocument();
|
||||
});
|
||||
first.unmount();
|
||||
|
||||
localStorage.setItem(taskViewStorageKey(), "board");
|
||||
const second = render(<App />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle("Board view").className).toContain("active");
|
||||
});
|
||||
second.unmount();
|
||||
|
||||
localStorage.setItem(taskViewStorageKey(), "plugin:fusion-plugin-dependency-graph:graph");
|
||||
(fetchPluginDashboardViews as ReturnType<typeof vi.fn>).mockResolvedValueOnce([
|
||||
{
|
||||
pluginId: "fusion-plugin-dependency-graph",
|
||||
view: { viewId: "graph", label: "Graph", componentPath: "./GraphView", placement: "more" },
|
||||
},
|
||||
]);
|
||||
render(<App />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Plugin view unavailable")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
localStorage.removeItem(taskViewStorageKey());
|
||||
localStorage.removeItem("kb-dashboard-view-mode");
|
||||
});
|
||||
|
||||
|
||||
it("opens planning mode when TodoView triggers planning from todo item", async () => {
|
||||
localStorage.setItem("kb-dashboard-view-mode", "project");
|
||||
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
|
||||
@@ -101,4 +101,19 @@ describe("usePluginDashboardViews", () => {
|
||||
await waitFor(() => expect(second.result.current.loading).toBe(false));
|
||||
expect(mockFetch).toHaveBeenCalledWith("project-b");
|
||||
});
|
||||
|
||||
it("supports filtering view entries by pluginId in consumers", async () => {
|
||||
mockFetch.mockResolvedValueOnce([
|
||||
{ pluginId: "fusion-plugin-dependency-graph", view: { viewId: "graph", label: "Graph", componentPath: "./Graph.js" } },
|
||||
{ pluginId: "fusion-plugin-queue", view: { viewId: "queue", label: "Queue", componentPath: "./Queue.js" } },
|
||||
]);
|
||||
|
||||
const { result } = renderHook(() => usePluginDashboardViews("project-a"));
|
||||
await waitFor(() => expect(result.current.loading).toBe(false));
|
||||
|
||||
const graphViews = result.current.views.filter((entry) => entry.pluginId === "fusion-plugin-dependency-graph");
|
||||
expect(graphViews).toEqual([
|
||||
{ pluginId: "fusion-plugin-dependency-graph", view: { viewId: "graph", label: "Graph", componentPath: "./Graph.js" } },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -367,6 +367,34 @@ describe("useViewState", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("round-trips between built-in and plugin task views", async () => {
|
||||
localStorage.setItem("kb:proj_123:kb-dashboard-task-view", "board");
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useViewState(
|
||||
createOptions({
|
||||
currentProject: PROJECT,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.taskView).toBe("board");
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
result.current.handleChangeTaskView("plugin:fusion-plugin-dependency-graph:graph");
|
||||
});
|
||||
expect(result.current.taskView).toBe("plugin:fusion-plugin-dependency-graph:graph");
|
||||
expect(localStorage.getItem("kb:proj_123:kb-dashboard-task-view")).toBe("plugin:fusion-plugin-dependency-graph:graph");
|
||||
|
||||
await act(async () => {
|
||||
result.current.handleChangeTaskView("board");
|
||||
});
|
||||
expect(result.current.taskView).toBe("board");
|
||||
expect(localStorage.getItem("kb:proj_123:kb-dashboard-task-view")).toBe("board");
|
||||
});
|
||||
|
||||
it("restores legacy views (board/list/agents/missions/chat) from scoped storage", async () => {
|
||||
const legacyViews = ["board", "list", "agents", "missions", "chat"] as const;
|
||||
|
||||
|
||||
@@ -34,6 +34,22 @@ describe("pluginViewRegistry", () => {
|
||||
expect(getPluginViewComponent("plugin-b", "missing")).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps all registered plugin views discoverable by key iteration", () => {
|
||||
const ViewA = lazy(async () => ({ default: () => <div>A</div> }));
|
||||
const ViewB = lazy(async () => ({ default: () => <div>B</div> }));
|
||||
registerPluginView("plugin-a", "main", ViewA);
|
||||
registerPluginView("plugin-b", "graph", ViewB);
|
||||
|
||||
const knownIds = [getPluginViewId("plugin-a", "main"), getPluginViewId("plugin-b", "graph")];
|
||||
const resolved = knownIds.map((id) => {
|
||||
const parsed = parsePluginViewId(id);
|
||||
if (!parsed) return null;
|
||||
return getPluginViewComponent(parsed.pluginId, parsed.viewId);
|
||||
});
|
||||
|
||||
expect(resolved).toEqual([ViewA, ViewB]);
|
||||
});
|
||||
|
||||
it("renders registered components", async () => {
|
||||
const View = lazy(async () => ({ default: () => <div>Rendered Plugin View</div> }));
|
||||
registerPluginView("plugin-a", "main", View);
|
||||
|
||||
@@ -784,6 +784,8 @@ describe("GET /api/plugins/dashboard-views", () => {
|
||||
componentPath: "./views/Graph.js",
|
||||
icon: "Network",
|
||||
placement: "more",
|
||||
order: 40,
|
||||
description: "Dependency graph",
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -794,6 +796,28 @@ describe("GET /api/plugins/dashboard-views", () => {
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toEqual(mockViews);
|
||||
});
|
||||
|
||||
it("returns exactly pluginLoader dashboard-view entries (no synthesized plugin rows)", async () => {
|
||||
(pluginLoader.getPluginDashboardViews as ReturnType<typeof vi.fn>).mockReturnValue([
|
||||
{
|
||||
pluginId: "with-view",
|
||||
view: {
|
||||
viewId: "graph",
|
||||
label: "Graph",
|
||||
componentPath: "./Graph.js",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const res = await performGet(buildApp(), "/api/plugins/dashboard-views");
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toHaveLength(1);
|
||||
expect(res.body[0]).toMatchObject({
|
||||
pluginId: "with-view",
|
||||
view: { viewId: "graph", label: "Graph", componentPath: "./Graph.js" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/plugins/ui-slots", () => {
|
||||
|
||||
Reference in New Issue
Block a user