Two UX wrinkles surfaced while exercising the CORE-Logos Studio: - DAG viewport: tall, few-layer graphs (e.g. the 408x1890, 2-layer/55-node alignment fan) were squished by viewBox-meet into an unreadable sliver. Render at natural height (scale ~1) inside a bounded, scrollable box; small graphs are unaffected (>= the caller height). Golden layout unchanged (layoutDag untouched). - Click-to-copy: new shared useCopyToClipboard hook gives every copy affordance a tooltip + transient confirmation. StatusFooter SHA now confirms 'Copied' (was a silent copy that read as 'does nothing'); 'Read Only' is labeled a non-interactive status (not a toggle); checkpoint-revision gains an explanatory tooltip. DigestBadge + MetadataTable copy buttons route through the hook + gain titles. Full workbench-ui vitest: 520 passed (+4), clean exit; tsc + build clean.
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { QueryClientProvider } from "@tanstack/react-query";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { createTestQueryClient } from "../test/createTestQueryClient";
|
|
import type { RuntimeStatus } from "../types/api";
|
|
|
|
vi.mock("../api/queries", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("../api/queries")>();
|
|
return { ...actual, useRuntimeStatus: vi.fn() };
|
|
});
|
|
|
|
import { useRuntimeStatus } from "../api/queries";
|
|
import { StatusFooter } from "./StatusFooter";
|
|
|
|
const status: RuntimeStatus = {
|
|
backend: "numpy",
|
|
git_revision: "5474a152057d9999",
|
|
engine_state_present: false,
|
|
checkpoint_revision: "unknown",
|
|
revision_warning: false,
|
|
active_session_id: null,
|
|
mutation_mode: "read_only",
|
|
};
|
|
|
|
function renderFooter() {
|
|
return render(
|
|
<QueryClientProvider client={createTestQueryClient()}>
|
|
<StatusFooter />
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.mocked(useRuntimeStatus).mockReturnValue({
|
|
data: status,
|
|
isError: false,
|
|
} as unknown as ReturnType<typeof useRuntimeStatus>);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe("StatusFooter", () => {
|
|
it("labels Read Only as a non-interactive status, not a toggle", () => {
|
|
renderFooter();
|
|
const chip = screen.getByText("Read Only");
|
|
expect(chip).toHaveAttribute(
|
|
"title",
|
|
"Runtime mutation mode — read-only by design (status, not a toggle)",
|
|
);
|
|
expect(chip.tagName).toBe("SPAN"); // status display, never a button
|
|
});
|
|
|
|
it("copies the full SHA and shows a transient confirmation on click", async () => {
|
|
// userEvent.setup() installs its own clipboard stub; read it back to verify
|
|
// the full SHA was copied (not just the short display form).
|
|
const user = userEvent.setup();
|
|
renderFooter();
|
|
|
|
const sha = screen.getByTestId("git-revision");
|
|
expect(sha).toHaveAttribute("title", "Copy full git revision SHA");
|
|
expect(sha).toHaveTextContent("5474a152"); // short SHA before copy
|
|
|
|
await user.click(sha);
|
|
|
|
// The confirmation the silent-copy version lacked.
|
|
expect(await screen.findByText("Copied")).toBeInTheDocument();
|
|
expect(await navigator.clipboard.readText()).toBe("5474a152057d9999");
|
|
});
|
|
|
|
it("gives the checkpoint-revision toggle an explanatory tooltip", () => {
|
|
renderFooter();
|
|
const checkpoint = screen.getByTestId("checkpoint-revision");
|
|
expect(checkpoint.getAttribute("title")).toMatch(/checkpoint revision/i);
|
|
expect(checkpoint.getAttribute("title")).toMatch(/unknown/);
|
|
});
|
|
});
|