diff --git a/.changeset/sharp-graphs-stretch.md b/.changeset/sharp-graphs-stretch.md new file mode 100644 index 0000000000..383b55cbe7 --- /dev/null +++ b/.changeset/sharp-graphs-stretch.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": patch +--- + +Fix the bundled dependency graph plugin so the graph view fills the available dashboard width. diff --git a/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.css b/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.css index 6b3c389518..18270802fa 100644 --- a/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.css +++ b/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.css @@ -1,5 +1,8 @@ .dependency-graph { position: relative; + flex: 1 1 auto; + min-width: 0; + box-sizing: border-box; height: 100%; padding: var(--space-md); } diff --git a/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.test.tsx b/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.test.tsx index 02d7fb1b1e..166e074a29 100644 --- a/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.test.tsx +++ b/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.test.tsx @@ -1,3 +1,4 @@ +import { readFileSync } from "node:fs"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; import type { Task } from "@fusion/core"; @@ -16,6 +17,9 @@ const setGraphBounds = vi.fn(); const clearSavedPositions = vi.fn(); let mockSavedPositions: NodePositions | null = null; let resizeObserverCallbacks: ResizeObserverCallback[] = []; +let cssStyleElement: HTMLStyleElement | null = null; + +const dependencyGraphCss = readFileSync("src/DependencyGraph.css", "utf8"); vi.mock("@fusion/dashboard/app/components/TaskCard", () => ({ TaskCard: ({ task, onOpenDetail, disableDrag }: { task: Task; onOpenDetail: (task: Task) => void; disableDrag?: boolean }) => ( @@ -71,6 +75,26 @@ function readNodePosition(taskId: string): { left: number; top: number } { }; } +function renderInProjectContent(tasks: Task[], width = "100%"): HTMLElement { + const { container } = render( +
+ +
, + ); + const graph = container.querySelector(".dependency-graph") as HTMLElement | null; + if (!graph) throw new Error("missing dependency graph"); + return graph; +} + +function expectGraphToFillFlexParent(graph: HTMLElement): void { + const style = getComputedStyle(graph); + expect(style.flexGrow).toBe("1"); + expect(style.flexShrink).toBe("1"); + expect(style.flexBasis).toBe("auto"); + expect(style.minWidth).toBe("0px"); + expect(style.boxSizing).toBe("border-box"); +} + describe("DependencyGraph", () => { beforeEach(() => { fitToGraph.mockReset(); @@ -85,6 +109,9 @@ describe("DependencyGraph", () => { clearSavedPositions.mockReset(); mockSavedPositions = null; resizeObserverCallbacks = []; + cssStyleElement = document.createElement("style"); + cssStyleElement.textContent = dependencyGraphCss; + document.head.appendChild(cssStyleElement); vi.stubGlobal( "ResizeObserver", @@ -102,6 +129,8 @@ describe("DependencyGraph", () => { afterEach(() => { vi.unstubAllGlobals(); + cssStyleElement?.remove(); + cssStyleElement = null; cleanup(); }); @@ -110,6 +139,21 @@ describe("DependencyGraph", () => { expect(screen.getByText(/No active tasks/i)).toBeTruthy(); }); + it("fills its flex parent in the empty state", () => { + const graph = renderInProjectContent([], "100%"); + + expect(screen.getByText(/No active tasks/i)).toBeTruthy(); + expectGraphToFillFlexParent(graph); + }); + + it("fills its flex parent with populated tasks on a mobile-width surface", () => { + const graph = renderInProjectContent([createTask("A", "todo"), createTask("B", "todo", ["A"])], "375px"); + + expect(screen.getByTestId("graph-task-node-A")).toBeTruthy(); + expect(screen.getByTestId("graph-task-node-B")).toBeTruthy(); + expectGraphToFillFlexParent(graph); + }); + it("renders only triage/todo/in-progress/in-review nodes from mixed columns", () => { render(