Keep task-detail popups below global utility windows while preserving Activity menu visibility. - Route ordinary task-detail FloatingWindow instances through a lower task-detail z-index band. - Emit and consume popup geometry-change signals so root-portaled Activity menus stay attached during drag and resize. - Extend dashboard tests and docs for popup layering and Activity dropdown behavior. Files changed: .changeset/fn-7493-task-popup-layer.md | 7 +++ docs/dashboard-guide.md | 8 ++- docs/settings-reference.md | 2 +- packages/dashboard/app/App.tsx | 4 ++ .../App.taskDetailFloatingGeometry.test.tsx | 45 +++++++++++++- .../dashboard/app/components/FloatingWindow.tsx | 31 ++++++++-- .../dashboard/app/components/TaskDetailModal.css | 3 + .../dashboard/app/components/TaskDetailModal.tsx | 27 ++++++++- .../components/__tests__/FloatingWindow.test.tsx | 32 +++++++++- .../FloatingWindowStack.cross-type.test.tsx | 48 ++++++++++++--- .../TaskDetailModal.task-activity-chat.test.tsx | 68 ++++++++++++++++++++++ .../app/components/floatingWindowStack.ts | 22 +++++-- 12 files changed, 272 insertions(+), 25 deletions(-) Fusion-Task-Id: FN-7493 Fusion-Task-Lineage: 5f1ec72c-f8dc-44ee-b303-319e0faac0f9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
155 lines
5.5 KiB
TypeScript
155 lines
5.5 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import { describe, expect, it, beforeEach } from "vitest";
|
|
import { TASK_DETAIL_FLOATING_GEOMETRY_KEY } from "../App";
|
|
import { FloatingWindow } from "../components/FloatingWindow";
|
|
|
|
function renderTaskDetailPopup(taskId: string) {
|
|
return render(
|
|
<FloatingWindow
|
|
windowKey={`task-detail-${taskId}`}
|
|
title={taskId}
|
|
onClose={() => {}}
|
|
hideHeader
|
|
dragHandleSelector=".task-detail-content--embedded > .modal-header"
|
|
className="floating-window--task-detail"
|
|
persistGeometryKey={TASK_DETAIL_FLOATING_GEOMETRY_KEY}
|
|
layer="task-detail"
|
|
>
|
|
<div className="task-detail-content--embedded">
|
|
<div className="modal-header">{taskId}</div>
|
|
<div>Task detail body</div>
|
|
</div>
|
|
</FloatingWindow>
|
|
);
|
|
}
|
|
|
|
describe("task-detail FloatingWindow geometry", () => {
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
});
|
|
|
|
it("uses one stable task-detail persistence key across different task window identities", () => {
|
|
expect(TASK_DETAIL_FLOATING_GEOMETRY_KEY).toBe("floating-window:task-detail");
|
|
});
|
|
|
|
it("restores the saved task popup size and position when a different task opens", () => {
|
|
localStorage.setItem(
|
|
TASK_DETAIL_FLOATING_GEOMETRY_KEY,
|
|
JSON.stringify({
|
|
size: { width: 684, height: 512 },
|
|
position: { x: 144, y: 88 },
|
|
}),
|
|
);
|
|
|
|
const first = renderTaskDetailPopup("FN-7459-A");
|
|
const firstPanel = screen.getByTestId("floating-window-task-detail-FN-7459-A");
|
|
expect(firstPanel.style.width).toBe("684px");
|
|
expect(firstPanel.style.height).toBe("512px");
|
|
expect(firstPanel.style.left).toBe("144px");
|
|
expect(firstPanel.style.top).toBe("88px");
|
|
|
|
first.unmount();
|
|
renderTaskDetailPopup("FN-7459-B");
|
|
|
|
const secondPanel = screen.getByTestId("floating-window-task-detail-FN-7459-B");
|
|
expect(secondPanel.style.width).toBe("684px");
|
|
expect(secondPanel.style.height).toBe("512px");
|
|
expect(secondPanel.style.left).toBe("144px");
|
|
expect(secondPanel.style.top).toBe("88px");
|
|
expect(secondPanel).toHaveClass("floating-window--task-detail");
|
|
});
|
|
|
|
it("raises multiple task-detail popups within the board layer below utility windows", () => {
|
|
render(
|
|
<>
|
|
<FloatingWindow
|
|
windowKey="task-detail-FN-7493-A"
|
|
title="FN-7493-A"
|
|
onClose={() => {}}
|
|
className="floating-window--task-detail"
|
|
persistGeometryKey={TASK_DETAIL_FLOATING_GEOMETRY_KEY}
|
|
layer="task-detail"
|
|
>
|
|
<div>task a body</div>
|
|
</FloatingWindow>
|
|
<FloatingWindow
|
|
windowKey="task-detail-FN-7493-B"
|
|
title="FN-7493-B"
|
|
onClose={() => {}}
|
|
className="floating-window--task-detail"
|
|
persistGeometryKey={TASK_DETAIL_FLOATING_GEOMETRY_KEY}
|
|
layer="task-detail"
|
|
>
|
|
<div>task b body</div>
|
|
</FloatingWindow>
|
|
<FloatingWindow windowKey="utility-FN-7493" title="Utility" onClose={() => {}}>
|
|
<div>utility body</div>
|
|
</FloatingWindow>
|
|
</>,
|
|
);
|
|
|
|
const firstTask = screen.getByTestId("floating-window-task-detail-FN-7493-A");
|
|
const secondTask = screen.getByTestId("floating-window-task-detail-FN-7493-B");
|
|
const utility = screen.getByTestId("floating-window-utility-FN-7493");
|
|
|
|
expect(Number(secondTask.style.zIndex)).toBeGreaterThan(Number(firstTask.style.zIndex));
|
|
expect(Number(secondTask.style.zIndex)).toBeLessThan(Number(utility.style.zIndex));
|
|
});
|
|
|
|
it("keeps task-detail geometry and layer isolated from non-task FloatingWindow keys", () => {
|
|
localStorage.setItem(
|
|
TASK_DETAIL_FLOATING_GEOMETRY_KEY,
|
|
JSON.stringify({
|
|
size: { width: 650, height: 490 },
|
|
position: { x: 118, y: 76 },
|
|
}),
|
|
);
|
|
localStorage.setItem(
|
|
"floating-window:mission-interview",
|
|
JSON.stringify({
|
|
size: { width: 540, height: 420 },
|
|
position: { x: 210, y: 120 },
|
|
}),
|
|
);
|
|
|
|
render(
|
|
<>
|
|
<FloatingWindow
|
|
windowKey="task-detail-FN-7459"
|
|
title="FN-7459"
|
|
onClose={() => {}}
|
|
className="floating-window--task-detail"
|
|
persistGeometryKey={TASK_DETAIL_FLOATING_GEOMETRY_KEY}
|
|
layer="task-detail"
|
|
>
|
|
<div>task detail body</div>
|
|
</FloatingWindow>
|
|
<FloatingWindow
|
|
windowKey="mission"
|
|
title="Mission"
|
|
onClose={() => {}}
|
|
persistGeometryKey="floating-window:mission-interview"
|
|
>
|
|
<div>mission body</div>
|
|
</FloatingWindow>
|
|
</>
|
|
);
|
|
|
|
const taskPanel = screen.getByTestId("floating-window-task-detail-FN-7459");
|
|
const taskOverlay = screen.getByTestId("floating-window-overlay-task-detail-FN-7459");
|
|
expect(taskPanel.style.width).toBe("650px");
|
|
expect(taskPanel.style.height).toBe("490px");
|
|
expect(taskPanel.style.left).toBe("118px");
|
|
expect(taskPanel.style.top).toBe("76px");
|
|
|
|
const missionPanel = screen.getByTestId("floating-window-mission");
|
|
const missionOverlay = screen.getByTestId("floating-window-overlay-mission");
|
|
expect(Number(taskPanel.style.zIndex)).toBeLessThan(Number(missionPanel.style.zIndex));
|
|
expect(Number(taskOverlay.style.zIndex)).toBeLessThan(Number(missionOverlay.style.zIndex));
|
|
expect(missionPanel.style.width).toBe("540px");
|
|
expect(missionPanel.style.height).toBe("420px");
|
|
expect(missionPanel.style.left).toBe("210px");
|
|
expect(missionPanel.style.top).toBe("120px");
|
|
});
|
|
});
|