feat(workbench): dock task and chart workspaces
Generic Container CI/CD / test-build-publish (push) Successful in 1m13s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 1m14s

This commit is contained in:
2026-11-12 11:42:25 +08:00
parent 33f0b90a7d
commit 7a304a91a8
27 changed files with 766 additions and 201 deletions
+60
View File
@@ -1,6 +1,8 @@
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, expect, it } from "vitest";
import { FloatingWindow } from "./floating-window";
import { floatingWindowEntries, floatingWindowOrder } from "./floating-window-layer";
import { useCallback, useRef, useState } from "react";
afterEach(cleanup);
@@ -35,3 +37,61 @@ it("retains manual opening, Escape dismissal and focus return", () => {
fireEvent.click(trigger);
expect(screen.getByRole("dialog", { name: "压力分析" })).toBeVisible();
});
function DockFixture({ multiple = false }: { multiple?: boolean }) {
const target = useRef<HTMLDivElement>(null);
const [activeId, setActiveId] = useState<string | null>(null);
const activate = useCallback((id: string) => setActiveId(id), []);
const deactivate = useCallback((id?: string) => setActiveId((current) => !id || current === id ? null : current), []);
return <>
<div ref={target} data-testid="workspace-slot" />
<button onClick={() => deactivate()}>切换工作区</button>
<FloatingWindow title="停靠分析" dock={{ target, activeId, activate, deactivate, view: "map_split", setView: () => undefined }}>
{() => <input aria-label="分析备注" />}
</FloatingWindow>
{multiple ? <FloatingWindow title="停靠对比" dock={{ target, activeId, activate, deactivate, view: "map_split", setView: () => undefined }}>
{() => <p>第二个来源</p>}
</FloatingWindow> : null}
</>;
}
it("preserves the live document when a docked window is hidden and located again", () => {
render(<DockFixture />);
const trigger = screen.getByRole("button", { name: "放大查看 停靠分析" });
fireEvent.click(trigger);
const dialog = screen.getByRole("dialog", { name: "停靠分析" });
expect(screen.getByTestId("workspace-slot")).toContainElement(dialog);
expect(dialog).toHaveAttribute("data-window-presentation", "docked");
expect(floatingWindowEntries()).toEqual([]);
expect(floatingWindowOrder()).toEqual([]);
const editor = screen.getByLabelText("分析备注");
fireEvent.change(editor, { target: { value: "保留核查说明" } });
fireEvent.click(screen.getByRole("button", { name: "切换工作区" }));
expect(dialog).not.toBeVisible();
fireEvent.click(trigger);
expect(screen.getByLabelText("分析备注")).toBe(editor);
expect(editor).toHaveValue("保留核查说明");
expect(dialog).toBeVisible();
expect(floatingWindowEntries()).toEqual([]);
expect(floatingWindowOrder()).toEqual([]);
fireEvent.keyDown(dialog, { key: "Escape" });
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
it("hides inactive docking hosts so another source can occupy the same workspace", () => {
render(<DockFixture multiple />);
fireEvent.click(screen.getByRole("button", { name: "放大查看 停靠分析" }));
const first = screen.getByRole("dialog", { name: "停靠分析" });
const editor = screen.getByLabelText("分析备注");
fireEvent.change(editor, { target: { value: "保留第一个来源" } });
fireEvent.click(screen.getByRole("button", { name: "放大查看 停靠对比" }));
const second = screen.getByRole("dialog", { name: "停靠对比" });
expect(first.parentElement).not.toBeVisible();
expect(first.parentElement).toHaveAttribute("hidden");
expect(second.parentElement).toBeVisible();
fireEvent.click(screen.getByRole("button", { name: "放大查看 停靠分析" }));
expect(first.parentElement).toBeVisible();
expect(second.parentElement).not.toBeVisible();
expect(screen.getByLabelText("分析备注")).toBe(editor);
expect(editor).toHaveValue("保留第一个来源");
});