Deletes the vanilla shell and mounts App.tsx on #root. main.ts and the 23 views it drove are gone; App.tsx composes the ported components — sidebar, multi-view, command bar, spawn modal, help and restart overlays, daemon link, drawer — around the catalog and layout stores. The pure parts of four deleted modules moved next to the components that still use them: the spawn-form model and spawnSimilar into components/spawn, the transcript's markdown/format helpers and the diff into components/structured, the help overlay's grouping into components/dialogs. Document-level event names now live in uiEvents.ts. Deduplicated two pairs the migration left side by side: the sidebar uses dialogs/KillDialog and common/ContextMenu. It also listens for the command bar's kill/rename events, which nothing had picked up since catalog.ts went away. styles.css stays as the token and base layer, with `#app` renamed to `#root` and the pane-body rules re-keyed off the classes the React panes actually carry. Closes #253
155 lines
4.5 KiB
TypeScript
155 lines
4.5 KiB
TypeScript
// @vitest-environment happy-dom
|
|
import { act, cleanup, fireEvent, render } from "@testing-library/react";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { OPEN_SPAWN_EVENT, SPAWN_SIMILAR_EVENT } from "../../uiEvents.js";
|
|
import { TOGGLE_DRAWER_EVENT } from "../../uiEvents.js";
|
|
import { Drawer } from "./Drawer.js";
|
|
|
|
/** Force the mobile media query on, whatever happy-dom's viewport says
|
|
* (mirrors ../layout/MultiView's own `forceMobile` in multiview.test.ts). */
|
|
function forceMobile(matches: boolean): void {
|
|
vi.spyOn(window, "matchMedia").mockImplementation(
|
|
(query: string) =>
|
|
({
|
|
matches: query.includes("859") && matches,
|
|
media: query,
|
|
onchange: null,
|
|
addEventListener() {},
|
|
removeEventListener() {},
|
|
addListener() {},
|
|
removeListener() {},
|
|
dispatchEvent: () => false,
|
|
}) as unknown as MediaQueryList,
|
|
);
|
|
}
|
|
|
|
function shell(): HTMLElement {
|
|
return document.querySelector(".app-shell") as HTMLElement;
|
|
}
|
|
|
|
function touch(type: "touchstart" | "touchend", x: number, y: number): void {
|
|
const touches = [{ clientX: x, clientY: y }];
|
|
const event = new Event(type, { bubbles: true }) as unknown as {
|
|
touches: typeof touches;
|
|
changedTouches: typeof touches;
|
|
};
|
|
event.touches = touches;
|
|
event.changedTouches = touches;
|
|
act(() => {
|
|
document.dispatchEvent(event as unknown as Event);
|
|
});
|
|
}
|
|
|
|
beforeEach(() => forceMobile(true));
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
function openViaToggle(): void {
|
|
act(() => {
|
|
document.dispatchEvent(new CustomEvent(TOGGLE_DRAWER_EVENT));
|
|
});
|
|
}
|
|
|
|
describe("Drawer (#35)", () => {
|
|
it("toggles open on the toggle event, off on desktop by class alone", () => {
|
|
render(
|
|
<Drawer sidebar={<div className="tree-leaf">row</div>}>
|
|
<div>content</div>
|
|
</Drawer>,
|
|
);
|
|
expect(shell().classList.contains("drawer-open")).toBe(false);
|
|
openViaToggle();
|
|
expect(shell().classList.contains("drawer-open")).toBe(true);
|
|
});
|
|
|
|
it("closes when a session row is tapped", () => {
|
|
render(
|
|
<Drawer sidebar={<div className="tree-leaf">row</div>}>
|
|
<div>content</div>
|
|
</Drawer>,
|
|
);
|
|
openViaToggle();
|
|
fireEvent.click(document.querySelector(".tree-leaf") as HTMLElement);
|
|
expect(shell().classList.contains("drawer-open")).toBe(false);
|
|
});
|
|
|
|
it("closes when a spawn is requested", () => {
|
|
render(
|
|
<Drawer sidebar={<div className="tree-leaf">row</div>}>
|
|
<div>content</div>
|
|
</Drawer>,
|
|
);
|
|
openViaToggle();
|
|
act(() => {
|
|
document.dispatchEvent(new CustomEvent(OPEN_SPAWN_EVENT));
|
|
});
|
|
expect(shell().classList.contains("drawer-open")).toBe(false);
|
|
});
|
|
|
|
it("closes when spawn similar is requested", () => {
|
|
render(
|
|
<Drawer sidebar={<div className="tree-leaf">row</div>}>
|
|
<div>content</div>
|
|
</Drawer>,
|
|
);
|
|
openViaToggle();
|
|
act(() => {
|
|
document.dispatchEvent(new CustomEvent(SPAWN_SIMILAR_EVENT, { detail: {} }));
|
|
});
|
|
expect(shell().classList.contains("drawer-open")).toBe(false);
|
|
});
|
|
|
|
it("closes on a backdrop click", () => {
|
|
render(
|
|
<Drawer sidebar={<div className="tree-leaf">row</div>}>
|
|
<div>content</div>
|
|
</Drawer>,
|
|
);
|
|
openViaToggle();
|
|
fireEvent.click(document.querySelector(".mv-backdrop") as HTMLElement);
|
|
expect(shell().classList.contains("drawer-open")).toBe(false);
|
|
});
|
|
|
|
it("opens on a rightward swipe and closes on a leftward one, only on mobile", () => {
|
|
render(
|
|
<Drawer sidebar={<div className="tree-leaf">row</div>}>
|
|
<div>content</div>
|
|
</Drawer>,
|
|
);
|
|
touch("touchstart", 0, 100);
|
|
touch("touchend", 100, 100);
|
|
expect(shell().classList.contains("drawer-open")).toBe(true);
|
|
|
|
touch("touchstart", 100, 100);
|
|
touch("touchend", 0, 100);
|
|
expect(shell().classList.contains("drawer-open")).toBe(false);
|
|
});
|
|
|
|
it("ignores swipes on desktop", () => {
|
|
forceMobile(false);
|
|
render(
|
|
<Drawer sidebar={<div className="tree-leaf">row</div>}>
|
|
<div>content</div>
|
|
</Drawer>,
|
|
);
|
|
touch("touchstart", 0, 100);
|
|
touch("touchend", 100, 100);
|
|
expect(shell().classList.contains("drawer-open")).toBe(false);
|
|
});
|
|
|
|
it("ignores a mostly-vertical or too-short gesture", () => {
|
|
render(
|
|
<Drawer sidebar={<div className="tree-leaf">row</div>}>
|
|
<div>content</div>
|
|
</Drawer>,
|
|
);
|
|
touch("touchstart", 0, 0);
|
|
touch("touchend", 10, 100);
|
|
expect(shell().classList.contains("drawer-open")).toBe(false);
|
|
});
|
|
});
|