fabro/apps/fabro-web/app/components/run-dock.test.tsx
2026-08-01 09:24:34 -04:00

181 lines
5.4 KiB
TypeScript

import {
afterEach,
beforeEach,
describe,
expect,
mock,
test,
} from "bun:test";
import TestRenderer, { act } from "react-test-renderer";
import { setupReactTestEnv } from "../lib/test-utils";
import { DockComposer, RunDockShell } from "./run-dock";
const mountedRenderers: TestRenderer.ReactTestRenderer[] = [];
let teardownReactEnv: (() => void) | undefined;
beforeEach(() => {
teardownReactEnv = setupReactTestEnv();
});
afterEach(() => {
for (const renderer of mountedRenderers.splice(0)) {
act(() => renderer.unmount());
}
teardownReactEnv?.();
teardownReactEnv = undefined;
});
describe("RunDockShell", () => {
function mountShell(
collapsed: boolean,
onCollapsedChange: (collapsed: boolean) => void,
) {
let renderer!: TestRenderer.ReactTestRenderer;
act(() => {
renderer = TestRenderer.create(
<RunDockShell
label="Steer running agent"
tone="idle"
status="Steering"
collapsed={collapsed}
onCollapsedChange={onCollapsedChange}
actions={<div />}
/>,
);
});
mountedRenderers.push(renderer);
return renderer;
}
function clickableDivs(renderer: TestRenderer.ReactTestRenderer) {
return renderer.root.findAll(
(node) => node.type === "div" && node.props.onClick !== undefined,
);
}
test("the whole collapsed bar is a click target that expands it", () => {
const onCollapsedChange = mock((_collapsed: boolean) => undefined);
const renderer = mountShell(true, onCollapsedChange);
const [header] = clickableDivs(renderer);
expect(header).toBeDefined();
act(() => header.props.onClick({ target: { closest: () => null } }));
expect(onCollapsedChange).toHaveBeenCalledWith(false);
});
test("clicks on header buttons do not also expand the bar", () => {
const onCollapsedChange = mock((_collapsed: boolean) => undefined);
const renderer = mountShell(true, onCollapsedChange);
const [header] = clickableDivs(renderer);
act(() => header.props.onClick({ target: { closest: () => ({}) } }));
expect(onCollapsedChange).not.toHaveBeenCalled();
});
test("clicks with a non-element target still expand the bar", () => {
const onCollapsedChange = mock((_collapsed: boolean) => undefined);
const renderer = mountShell(true, onCollapsedChange);
const [header] = clickableDivs(renderer);
act(() => header.props.onClick({ target: {} }));
expect(onCollapsedChange).toHaveBeenCalledWith(false);
});
test("the expanded header is not a click target", () => {
const onCollapsedChange = mock((_collapsed: boolean) => undefined);
const renderer = mountShell(false, onCollapsedChange);
expect(clickableDivs(renderer)).toHaveLength(0);
});
});
describe("DockComposer", () => {
test("describes its keyboard behavior to assistive technology", () => {
let renderer!: TestRenderer.ReactTestRenderer;
act(() => {
renderer = TestRenderer.create(
<DockComposer
onSubmit={() => Promise.resolve(true)}
placeholder="Write a message"
submitLabel="Send"
submitting={false}
ariaLabel="Message"
/>,
);
});
mountedRenderers.push(renderer);
const textarea = renderer.root.findByType("textarea");
const instruction = renderer.root.findByProps({
id: textarea.props["aria-describedby"],
});
expect(instruction.children.join("")).toBe(
"Press Enter to send. Press Shift+Enter for a new line.",
);
expect(textarea.props.name).toBeUndefined();
});
test("does not submit Enter while an IME composition is active", async () => {
const onSubmit = mock(() => Promise.resolve(true));
const preventDefault = mock(() => undefined);
let renderer!: TestRenderer.ReactTestRenderer;
act(() => {
renderer = TestRenderer.create(
<DockComposer
onSubmit={onSubmit}
placeholder="Write a message"
submitLabel="Send"
submitting={false}
ariaLabel="Message"
/>,
);
});
mountedRenderers.push(renderer);
const textarea = renderer.root.findByType("textarea");
act(() => textarea.props.onChange({ target: { value: "draft" } }));
await act(async () => {
textarea.props.onKeyDown({
key: "Enter",
shiftKey: false,
nativeEvent: { isComposing: true },
preventDefault,
});
});
expect(preventDefault).not.toHaveBeenCalled();
expect(onSubmit).not.toHaveBeenCalled();
});
test("submits Enter after composition ends", async () => {
const onSubmit = mock(() => Promise.resolve(true));
const preventDefault = mock(() => undefined);
let renderer!: TestRenderer.ReactTestRenderer;
act(() => {
renderer = TestRenderer.create(
<DockComposer
onSubmit={onSubmit}
placeholder="Write a message"
submitLabel="Send"
submitting={false}
ariaLabel="Message"
/>,
);
});
mountedRenderers.push(renderer);
const textarea = renderer.root.findByType("textarea");
act(() => textarea.props.onChange({ target: { value: " ready " } }));
await act(async () => {
textarea.props.onKeyDown({
key: "Enter",
shiftKey: false,
nativeEvent: { isComposing: false },
preventDefault,
});
});
expect(preventDefault).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenCalledWith("ready");
});
});