Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/frontend/packages/swarm-ui/test/split-layout.test.mjs

240 lines
8.1 KiB
JavaScript

// The layout of a group of agent terminals: splitting, removing, resizing, naming
// and reading a saved group back. Run by `npm test` with Node's type stripping.
import assert from "node:assert/strict";
import { test } from "node:test";
import {
agentsIn,
groupTitle,
MIN_SHARE,
pane,
parseGroups,
pruneMissing,
removeAgent,
resizeAt,
sideAt,
splitPane,
groupHref,
groupIdOf,
terminalAgentOf,
} from "../src/agent/terminal/split-layout.ts";
const row = (children, sizes) => ({
kind: "split",
dir: "row",
children,
sizes: sizes ?? children.map(() => 1 / children.length),
});
const column = (children, sizes) => ({
...row(children, sizes),
dir: "column",
});
const near = (got, want) =>
assert.ok(Math.abs(got - want) < 1e-9, `${got} is not ${want}`);
test("dropping on the right edge puts the agent beside the pane, in half its box", () => {
const got = splitPane(pane("foo"), "foo", "bar", "right");
assert.deepEqual(got, row([pane("foo"), pane("bar")], [0.5, 0.5]));
});
test("the left and top edges put the agent before the pane, the bottom edge below it", () => {
assert.deepEqual(agentsIn(splitPane(pane("foo"), "foo", "bar", "left")), [
"bar",
"foo",
]);
assert.equal(splitPane(pane("foo"), "foo", "bar", "top").dir, "column");
assert.deepEqual(
splitPane(pane("foo"), "foo", "bar", "bottom"),
column([pane("foo"), pane("bar")], [0.5, 0.5]),
);
});
test("splitting again the same way adds a child and halves only the pane's share", () => {
const two = row([pane("a"), pane("b")], [0.7, 0.3]);
const got = splitPane(two, "a", "c", "right");
assert.equal(got.kind, "split");
assert.deepEqual(agentsIn(got), ["a", "c", "b"]);
near(got.sizes[0], 0.35);
near(got.sizes[1], 0.35);
near(got.sizes[2], 0.3);
});
test("splitting the other way nests a split inside the pane's box", () => {
const two = row([pane("a"), pane("b")], [0.6, 0.4]);
const got = splitPane(two, "b", "c", "bottom");
assert.deepEqual(got.sizes, [0.6, 0.4]);
assert.deepEqual(got.children[1], column([pane("b"), pane("c")], [0.5, 0.5]));
});
test("an agent already in the group is moved, not copied, and the gap closes", () => {
const three = row([pane("a"), pane("b"), pane("c")]);
const got = splitPane(three, "a", "c", "top");
assert.deepEqual(agentsIn(got).sort(), ["a", "b", "c"]);
assert.equal(agentsIn(got).filter((x) => x === "c").length, 1);
assert.equal(got.kind, "split");
assert.equal(got.dir, "row");
assert.deepEqual(got.children[0], column([pane("c"), pane("a")], [0.5, 0.5]));
});
test("dropping an agent on its own pane, or on a pane that is not there, changes nothing", () => {
const two = row([pane("a"), pane("b")]);
assert.equal(splitPane(two, "a", "a", "left"), two);
assert.equal(splitPane(two, "zzz", "c", "left"), two);
});
test("removing an agent hands its share to the others and a lone child replaces its split", () => {
const three = row([pane("a"), pane("b"), pane("c")], [0.5, 0.25, 0.25]);
const two = removeAgent(three, "a");
assert.deepEqual(two, row([pane("b"), pane("c")], [0.5, 0.5]));
assert.deepEqual(removeAgent(two, "b"), pane("c"));
assert.equal(removeAgent(pane("c"), "c"), null);
assert.equal(removeAgent(two, "nobody") !== null, true);
});
test("removing the last pane of a nested split lets its sibling take the place and merge upwards", () => {
const nested = row([pane("a"), column([pane("b"), pane("c")])], [0.4, 0.6]);
const got = removeAgent(nested, "c");
assert.deepEqual(got, row([pane("a"), pane("b")], [0.4, 0.6]));
// a column that ends up inside a column joins it
const deep = column([
row([pane("a"), column([pane("b"), pane("c")])]),
pane("d"),
]);
assert.deepEqual(agentsIn(removeAgent(deep, "a")), ["b", "c", "d"]);
assert.equal(removeAgent(deep, "a").dir, "column");
});
test("a divider moves share from one neighbour to the other and stops short of zero", () => {
const two = row([pane("a"), pane("b")], [0.5, 0.5]);
const moved = resizeAt(two, [], 0, 0.2);
near(moved.sizes[0], 0.7);
near(moved.sizes[1], 0.3);
const far = resizeAt(two, [], 0, 5);
near(far.sizes[0], 1 - MIN_SHARE);
near(far.sizes[1], MIN_SHARE);
const back = resizeAt(two, [], 0, -5);
near(back.sizes[0], MIN_SHARE);
assert.equal(resizeAt(two, [], 7, 0.1), two);
});
test("a divider of a nested split is found by its path", () => {
const nested = row([pane("a"), column([pane("b"), pane("c")])]);
const got = resizeAt(nested, [1], 0, 0.25);
assert.deepEqual(got.sizes, [0.5, 0.5]);
near(got.children[1].sizes[0], 0.75);
near(got.children[1].sizes[1], 0.25);
});
test("a saved layout loses the agents that no longer exist", () => {
const three = row([pane("a"), pane("b"), pane("c")]);
assert.deepEqual(agentsIn(pruneMissing(three, new Set(["a", "c"]))), [
"a",
"c",
]);
assert.equal(pruneMissing(three, new Set()), null);
assert.equal(pruneMissing(three, new Set(["a", "b", "c"])), three);
});
test("a group goes by its own name, else by its agents", () => {
const root = row([pane("foo"), pane("bar")]);
assert.equal(groupTitle({ id: "1", name: "", root }), "foo + bar");
assert.equal(groupTitle({ id: "1", name: " ", root }), "foo + bar");
assert.equal(groupTitle({ id: "1", name: "infra", root }), "infra");
});
test("a saved group is read back, and what is malformed of it is dropped", () => {
const good = {
id: "g1",
name: "infra",
root: {
kind: "split",
dir: "row",
sizes: [3, 1],
children: [
{ kind: "pane", agent: "a" },
{ kind: "pane", agent: "b" },
],
},
};
const [got] = parseGroups([good]);
assert.equal(got.name, "infra");
near(got.root.sizes[0], 0.75);
near(got.root.sizes[1], 0.25);
assert.deepEqual(parseGroups("nope"), []);
assert.deepEqual(
parseGroups([null, 5, { id: "", root: good.root }, { id: "x" }]),
[],
);
// the same agent twice, a bad direction, a bad child, missing sizes
const odd = parseGroups([
{
id: "g2",
root: {
kind: "split",
dir: "row",
children: [
{ kind: "pane", agent: "a" },
{ kind: "pane", agent: "a" },
{ kind: "pane", agent: "c" },
7,
],
},
},
{
id: "g3",
root: {
kind: "split",
dir: "diagonal",
children: [{ kind: "pane", agent: "a" }],
},
},
{ id: "g1", root: { kind: "pane", agent: "z" } },
]);
assert.deepEqual(
odd.map((g) => g.id),
["g2", "g1"],
);
assert.deepEqual(agentsIn(odd[0].root), ["a", "c"]);
near(odd[0].root.sizes[0], 0.5);
assert.equal(odd[0].name, "");
});
test("a drop falls on the nearest edge of the pane, the diagonals dividing the four", () => {
assert.equal(sideAt(0.1, 0.5), "left");
assert.equal(sideAt(0.9, 0.5), "right");
assert.equal(sideAt(0.5, 0.1), "top");
assert.equal(sideAt(0.5, 0.9), "bottom");
assert.equal(sideAt(0.3, 0.1), "top");
assert.equal(sideAt(0.1, 0.3), "left");
assert.equal(sideAt(0.9, 0.7), "right");
});
test("a group's header tab and an agent's terminal tab are told apart by their paths", () => {
assert.equal(groupHref("ab12"), "/agents/group/ab12");
assert.equal(groupIdOf("/agents/group/ab12"), "ab12");
assert.equal(groupIdOf("/agents/group/"), null);
assert.equal(groupIdOf("/agents/iris/terminal"), null);
assert.equal(terminalAgentOf("/agents/iris/terminal"), "iris");
assert.equal(terminalAgentOf("/agents/iris/subagents/x/terminal"), null);
assert.equal(terminalAgentOf("/agents/group/ab12"), null);
});
test("a pane already below the floor can grow but never shrinks further, and no share goes negative", () => {
const tiny = row([pane("a"), pane("b"), pane("c")], [0.005, 0.005, 0.99]);
const grown = resizeAt(tiny, [], 1, 0.5);
assert.ok(grown.sizes[1] > 0.005);
assert.ok(grown.sizes.every((s) => s >= 0));
near(
grown.sizes.reduce((x, y) => x + y, 0),
1,
);
const shrunk = resizeAt(tiny, [], 0, -0.5);
assert.deepEqual(shrunk.sizes, tiny.sizes);
const both = resizeAt(
row([pane("a"), pane("b")], [0.005, 0.005]),
[],
0,
0.3,
);
assert.ok(both.sizes.every((s) => s >= 0));
});