Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
137 lines
4.4 KiB
TypeScript
137 lines
4.4 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import type { WorkspaceNode } from "./models.ts";
|
|
import {
|
|
appendPaneToWorkspaceRoot,
|
|
cloneWorkspaceTree,
|
|
insertPaneIntoWorkspace,
|
|
reorderWorkspaceFocusSessionOrder,
|
|
resolveWorkspaceFocusSessionOrder,
|
|
} from "./workspace.ts";
|
|
|
|
const root: WorkspaceNode = {
|
|
id: "split-1",
|
|
type: "split",
|
|
direction: "vertical",
|
|
children: [
|
|
{ id: "pane-1", type: "pane", sessionId: "s1" },
|
|
{ id: "pane-2", type: "pane", sessionId: "s2" },
|
|
{ id: "pane-3", type: "pane", sessionId: "s3" },
|
|
],
|
|
};
|
|
|
|
test("resolveWorkspaceFocusSessionOrder follows tree order when no saved order exists", () => {
|
|
assert.deepEqual(resolveWorkspaceFocusSessionOrder(root), ["s1", "s2", "s3"]);
|
|
});
|
|
|
|
test("resolveWorkspaceFocusSessionOrder drops stale ids and appends new panes", () => {
|
|
assert.deepEqual(
|
|
resolveWorkspaceFocusSessionOrder(root, ["stale", "s3", "s1"]),
|
|
["s3", "s1", "s2"],
|
|
);
|
|
});
|
|
|
|
test("reorderWorkspaceFocusSessionOrder moves a session before a target", () => {
|
|
assert.deepEqual(
|
|
reorderWorkspaceFocusSessionOrder(root, undefined, "s3", "s1", "before"),
|
|
["s3", "s1", "s2"],
|
|
);
|
|
});
|
|
|
|
test("reorderWorkspaceFocusSessionOrder moves a session after a target", () => {
|
|
assert.deepEqual(
|
|
reorderWorkspaceFocusSessionOrder(root, ["s1", "s2", "s3"], "s1", "s3", "after"),
|
|
["s2", "s3", "s1"],
|
|
);
|
|
});
|
|
|
|
test("appendPaneToWorkspaceRoot ignores an existing session pane", () => {
|
|
assert.equal(appendPaneToWorkspaceRoot(root, "s2"), root);
|
|
});
|
|
|
|
test("insertPaneIntoWorkspace ignores an existing session pane", () => {
|
|
assert.equal(
|
|
insertPaneIntoWorkspace(root, "s2", {
|
|
direction: "vertical",
|
|
position: "right",
|
|
targetSessionId: "s1",
|
|
}),
|
|
root,
|
|
);
|
|
});
|
|
|
|
const nestedRoot: WorkspaceNode = {
|
|
id: "split-root",
|
|
type: "split",
|
|
direction: "vertical",
|
|
sizes: [0.8, 0.2],
|
|
children: [
|
|
{ id: "pane-a", type: "pane", sessionId: "s1" },
|
|
{
|
|
id: "split-inner",
|
|
type: "split",
|
|
direction: "horizontal",
|
|
sizes: [0.5, 0.5],
|
|
children: [
|
|
{ id: "pane-b", type: "pane", sessionId: "s2" },
|
|
{ id: "pane-c", type: "pane", sessionId: "s3" },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
test("cloneWorkspaceTree remaps every sessionId and preserves direction/sizes", () => {
|
|
const map = new Map([["s1", "n1"], ["s2", "n2"], ["s3", "n3"]]);
|
|
const clone = cloneWorkspaceTree(nestedRoot, map);
|
|
|
|
assert.equal(clone.type, "split");
|
|
if (clone.type !== "split") return;
|
|
assert.equal(clone.direction, "vertical");
|
|
assert.deepEqual(clone.sizes, [0.8, 0.2]);
|
|
const inner = clone.children[1];
|
|
assert.equal(inner.type, "split");
|
|
if (inner.type !== "split") return;
|
|
assert.equal(inner.direction, "horizontal");
|
|
assert.deepEqual(inner.sizes, [0.5, 0.5]);
|
|
|
|
const collect = (n: WorkspaceNode): string[] =>
|
|
n.type === "pane" ? [n.sessionId] : n.children.flatMap(collect);
|
|
assert.deepEqual(collect(clone), ["n1", "n2", "n3"]);
|
|
});
|
|
|
|
test("cloneWorkspaceTree mints fresh node ids and does not mutate the source", () => {
|
|
const map = new Map([["s1", "n1"], ["s2", "n2"], ["s3", "n3"]]);
|
|
const clone = cloneWorkspaceTree(nestedRoot, map);
|
|
assert.notEqual(clone.id, nestedRoot.id);
|
|
assert.equal(nestedRoot.id, "split-root");
|
|
const collectSrc = (n: WorkspaceNode): string[] =>
|
|
n.type === "pane" ? [n.sessionId] : n.children.flatMap(collectSrc);
|
|
assert.deepEqual(collectSrc(nestedRoot), ["s1", "s2", "s3"]);
|
|
|
|
const collectIds = (n: WorkspaceNode): string[] =>
|
|
n.type === "pane" ? [n.id] : [n.id, ...n.children.flatMap(collectIds)];
|
|
const srcIds = new Set(collectIds(nestedRoot));
|
|
assert.ok(collectIds(clone).every(id => !srcIds.has(id)));
|
|
});
|
|
|
|
test("cloneWorkspaceTree uses pre-minted node ids when supplied", () => {
|
|
const clone = cloneWorkspaceTree(
|
|
nestedRoot,
|
|
new Map([["s1", "n1"], ["s2", "n2"], ["s3", "n3"]]),
|
|
new Map([["split-root", "new-root"], ["pane-a", "new-a"], ["split-inner", "new-inner"], ["pane-b", "new-b"], ["pane-c", "new-c"]]),
|
|
);
|
|
assert.equal(clone.id, "new-root");
|
|
if (clone.type !== "split") return;
|
|
assert.equal(clone.children[0].id, "new-a");
|
|
assert.equal(clone.children[1].id, "new-inner");
|
|
});
|
|
|
|
test("cloneWorkspaceTree keeps original sessionId when the map lacks it", () => {
|
|
const clone = cloneWorkspaceTree(
|
|
{ id: "p", type: "pane", sessionId: "s1" },
|
|
new Map(),
|
|
);
|
|
assert.equal(clone.type === "pane" && clone.sessionId, "s1");
|
|
});
|