fix: improve console workspace usability
This commit is contained in:
@@ -3,13 +3,16 @@ import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { cleanup } from "@testing-library/react";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { initialState } from "../app/state.js";
|
||||
import { ConsoleShell } from "./ConsoleShell.js";
|
||||
|
||||
const globalStyles = readFileSync("src/styles/global.css", "utf8");
|
||||
|
||||
afterEach(() => cleanup());
|
||||
|
||||
describe("ConsoleShell", () => {
|
||||
it("renders the connection header, lifecycle rail, main content, and evidence surface", () => {
|
||||
it("renders the connection header, lifecycle rail, and main content without a global evidence panel", () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={["/console/discover"]}>
|
||||
<ConsoleShell
|
||||
@@ -27,13 +30,21 @@ describe("ConsoleShell", () => {
|
||||
expect(screen.getByRole("main", { name: "Console workspace" })).toHaveTextContent(
|
||||
"Discover content",
|
||||
);
|
||||
expect(screen.getByRole("complementary", { name: "Operation evidence" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("complementary", { name: "Operation evidence" })).toBeNull();
|
||||
expect(screen.getByRole("link", { name: "Discover" })).toHaveAttribute(
|
||||
"aria-current",
|
||||
"page",
|
||||
);
|
||||
});
|
||||
|
||||
it("reserves the workspace width for navigation and route content", () => {
|
||||
expect(globalStyles).toMatch(
|
||||
/\.console-workspace\s*\{[\s\S]*?grid-template-columns:\s*minmax\(9rem, 13rem\)\s+minmax\(0, 1fr\)/,
|
||||
);
|
||||
expect(globalStyles).toContain('"nav main"');
|
||||
expect(globalStyles).not.toContain('"nav main evidence"');
|
||||
});
|
||||
|
||||
it("renders all lifecycle links with route destinations", () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { ReactNode } from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import type { ConnectionState } from "../app/state.js";
|
||||
import { ConnectionHeader } from "../components/ConnectionHeader.js";
|
||||
import { EvidenceLedger } from "./EvidenceLedger.js";
|
||||
|
||||
type Props = {
|
||||
readonly connection: ConnectionState;
|
||||
@@ -59,9 +58,5 @@ export const ConsoleShell = ({
|
||||
<main id="console-workspace-main" aria-label="Console workspace" tabIndex={-1}>
|
||||
{children}
|
||||
</main>
|
||||
<aside aria-label="Operation evidence" className="console-workspace__evidence">
|
||||
<h2>Operation evidence</h2>
|
||||
<EvidenceLedger records={connection.evidence} />
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -149,13 +149,13 @@ describe("ConsoleWorkspace", () => {
|
||||
expect(screen.getByTestId("executor-state")).toHaveTextContent("available");
|
||||
expect(screen.getByTestId("write-executor-state")).toHaveTextContent("available");
|
||||
expect(screen.getByTestId("executor-stable")).toHaveTextContent("yes");
|
||||
expect(screen.getAllByText("Health check")).toHaveLength(1);
|
||||
expect(screen.getByTestId("evidence-ids")).toHaveTextContent("workflow.health-0");
|
||||
expect(mockedCallOperation).not.toHaveBeenCalled();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Navigate to drafts" }));
|
||||
|
||||
expect(await screen.findByText("Drafts route")).toBeInTheDocument();
|
||||
expect(screen.getAllByText("Health check")).toHaveLength(1);
|
||||
expect(screen.getByTestId("evidence-ids")).toHaveTextContent("workflow.health-0");
|
||||
expect(screen.getByTestId("connected-target")).toHaveTextContent("http://one.example/rpc");
|
||||
});
|
||||
|
||||
@@ -273,6 +273,6 @@ describe("ConsoleWorkspace", () => {
|
||||
"http://two.example/rpc",
|
||||
);
|
||||
});
|
||||
expect(screen.getAllByText("Health check")).toHaveLength(1);
|
||||
expect(screen.getByTestId("evidence-ids")).toHaveTextContent("workflow.health-0");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -188,6 +188,21 @@ describe("DraftWorkbench", () => {
|
||||
expect(within(inspector as HTMLElement).getByText("demo.collect")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("opens the context inspector when a route outcome is selected on mobile", async () => {
|
||||
setViewport(390);
|
||||
const user = userEvent.setup();
|
||||
const { container } = render(<DraftWorkbench draft={workspace} />);
|
||||
|
||||
const routes = screen.getByLabelText("Route outcomes");
|
||||
await user.click(within(routes).getByRole("button", { name: /collect.*ok/i }));
|
||||
|
||||
const inspector = container.querySelector("#draft-workbench-inspector");
|
||||
expect(inspector).toHaveAttribute("open", "");
|
||||
expect(within(inspector as HTMLElement).getByRole("heading", { name: "Route inspector" })).toBeInTheDocument();
|
||||
expect(within(inspector as HTMLElement).getByRole("textbox", { name: "Source step" })).toHaveValue("collect");
|
||||
expect(within(inspector as HTMLElement).getByRole("textbox", { name: "Outcome" })).toHaveValue("ok");
|
||||
});
|
||||
|
||||
it("keeps a dirty inspector form mounted and intact across mobile close and reopen", async () => {
|
||||
setViewport(390);
|
||||
mockedUseAuthoringCapabilityDetail.mockReturnValue({
|
||||
|
||||
@@ -164,8 +164,11 @@ export const DraftWorkbench = ({
|
||||
(nextSelection: WorkbenchSelection): void => {
|
||||
controller.select(nextSelection);
|
||||
onSelectionChange?.(nextSelection);
|
||||
if (isMobile && (nextSelection.kind === "edge" || nextSelection.kind === "node")) {
|
||||
setOpenSheet("inspector");
|
||||
}
|
||||
},
|
||||
[controller, onSelectionChange],
|
||||
[controller, isMobile, onSelectionChange],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { cleanup, render, screen, within } from "@testing-library/react";
|
||||
import { readFileSync } from "node:fs";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { MemoryRouter, Route, Routes, useLocation } from "react-router-dom";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -33,6 +34,7 @@ vi.mock("../domain/draft-authoring-client.js", () => ({
|
||||
|
||||
const mockedUseCapabilityDiscovery = vi.mocked(useCapabilityDiscovery);
|
||||
const mockedUseDraftWorkspace = vi.mocked(useDraftWorkspace);
|
||||
const globalStyles = readFileSync("src/styles/global.css", "utf8");
|
||||
const mockedUseConsoleWorkspace = vi.mocked(useConsoleWorkspace);
|
||||
const mockedCreateDraftAuthoringClient = vi.mocked(createDraftAuthoringClient);
|
||||
|
||||
@@ -335,4 +337,10 @@ describe("DiscoverRoute", () => {
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps both discovery panes scrollable within the viewport", () => {
|
||||
expect(globalStyles).toMatch(
|
||||
/\.capability-discovery__results,\s*\.capability-discovery__detail,\s*\.capability-playground\s*\{[\s\S]*?max-height:\s*calc\(100vh - 10rem\);[\s\S]*?overflow-y:\s*auto/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -150,6 +150,12 @@ describe("DraftDetailRoute", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps route outcomes scrollable inside the bounded graph panel", () => {
|
||||
expect(globalStyles).toMatch(
|
||||
/\.authoring-graph__routes\s*\{[\s\S]*?min-height:\s*0;[\s\S]*?overflow-y:\s*auto/,
|
||||
);
|
||||
});
|
||||
|
||||
it("explains when the full draft document was not returned", () => {
|
||||
mockedUseDraftWorkspace.mockReturnValue(
|
||||
controller({ selected: workspace({ draft: null }) }),
|
||||
|
||||
Reference in New Issue
Block a user