This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/web/src/persistence.test.ts
T
Miguel PalhasandClaude Opus 5 6be0dba74e feat(maestro): spawn form back in a modal on web
Draft panes are gone: an empty pane is a placeholder again, and every
spawn affordance opens a dialog around the shared spawn form. Escape,
Cancel, the ✕ and a backdrop click dismiss it; a successful submit puts
the session in a new tab (Spawn similar and the fast-forward fallback
keep placing it beside the pane they acted on).

Removes the per-leaf draft state, its persistence strip, and the form
reuse/teardown machinery the panes needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 14:44:55 +01:00

100 lines
3.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { hydrate, serializeLayout } from "./persistence.js";
import { leaves, type Layout } from "./layout.js";
describe("hydrate", () => {
it("rejects anything that is not a tab-bearing object", () => {
expect(hydrate(null)).toBeNull();
expect(hydrate("nope")).toBeNull();
expect(hydrate({})).toBeNull();
expect(hydrate({ tabs: [] })).toBeNull();
expect(hydrate({ tabs: "no" })).toBeNull();
});
it("rebuilds tabs and re-issues ids rather than trusting stored ones", () => {
const layout = hydrate({
tabs: [{ id: "old", title: "Work", root: { kind: "leaf", id: "l1", sessionId: "s1" } }],
activeTabId: "old",
});
expect(layout).not.toBeNull();
expect(layout!.tabs).toHaveLength(1);
expect(layout!.tabs[0].title).toBe("Work");
expect(layout!.tabs[0].id).not.toBe("old");
expect(layout!.activeTabId).toBe(layout!.tabs[0].id);
expect(leaves(layout!.tabs[0].root)[0].sessionId).toBe("s1");
});
it("carries the focused leaf across the id reissue", () => {
const layout = hydrate({
tabs: [
{
id: "t",
root: {
kind: "split",
dir: "row",
a: { kind: "leaf", id: "l1", sessionId: "s1" },
b: { kind: "leaf", id: "l2", sessionId: "s2" },
},
focusedLeafId: "l2",
},
],
activeTabId: "t",
});
const tab = layout!.tabs[0];
const focused = leaves(tab.root).find((l) => l.id === tab.focusedLeafId);
expect(focused?.sessionId).toBe("s2");
});
it("falls back to a default title and the first tab as active", () => {
const layout = hydrate({ tabs: [{ id: "a" }, { id: "b" }], activeTabId: "missing" });
expect(layout!.tabs[0].title).toBe("Tab 1");
expect(layout!.activeTabId).toBe(layout!.tabs[0].id);
});
it("drops a malformed split branch instead of failing the whole layout", () => {
const layout = hydrate({
tabs: [{ id: "t", root: { kind: "split", dir: "row", a: null, b: { kind: "leaf", id: "l", sessionId: "s" } } }],
activeTabId: "t",
});
expect(leaves(layout!.tabs[0].root).map((l) => l.sessionId)).toEqual(["s"]);
});
it("ignores leaf fields a stale blob carries that the layout no longer has", () => {
const layout = hydrate({
tabs: [
{ id: "t", root: { kind: "leaf", id: "l1", sessionId: null, draft: { command: "zsh", cwd: "/x" } } },
],
activeTabId: "t",
});
expect(leaves(layout!.tabs[0].root)[0]).not.toHaveProperty("draft");
});
});
describe("serializeLayout", () => {
it("round-trips the tree, its sessions and its split ratios", () => {
const layout = hydrate({
tabs: [
{
id: "t",
root: {
kind: "split",
dir: "row",
ratio: 0.3,
a: { kind: "leaf", id: "l1", sessionId: "s1" },
b: { kind: "leaf", id: "l2", sessionId: null },
},
},
],
activeTabId: "t",
}) as Layout;
const restored = hydrate(JSON.parse(serializeLayout(layout)).layout);
expect(restored).not.toBeNull();
expect(leaves(restored!.tabs[0].root).map((l) => l.sessionId)).toEqual(["s1", null]);
expect((restored!.tabs[0].root as { ratio: number }).ratio).toBe(0.3);
});
});