Skip to content

Commit d39e6cb

Browse files
committed
Merge branch 'feat/workspace-navigation-shortcuts' into develop
2 parents a02d189 + a415d01 commit d39e6cb

10 files changed

Lines changed: 1123 additions & 17 deletions

File tree

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import { describe, expect, it } from "vitest";
2+
import type { PaneNode } from "./atoms/pane-layout";
3+
import { findAdjacentSessionId } from "./pane-navigation";
4+
5+
describe("pane-navigation", () => {
6+
it("finds horizontal neighbors in a simple split", () => {
7+
const layout: PaneNode = {
8+
id: "root",
9+
type: "split",
10+
direction: "horizontal",
11+
ratio: 0.5,
12+
children: [
13+
{ id: "left", type: "leaf", sessionId: "sess-left" },
14+
{ id: "right", type: "leaf", sessionId: "sess-right" },
15+
],
16+
};
17+
18+
expect(findAdjacentSessionId(layout, "sess-left", "right")).toBe("sess-right");
19+
expect(findAdjacentSessionId(layout, "sess-right", "left")).toBe("sess-left");
20+
});
21+
22+
it("returns null when no candidate exists in the requested direction", () => {
23+
const layout: PaneNode = {
24+
id: "root",
25+
type: "split",
26+
direction: "horizontal",
27+
ratio: 0.5,
28+
children: [
29+
{ id: "left", type: "leaf", sessionId: "sess-left" },
30+
{ id: "right", type: "leaf", sessionId: "sess-right" },
31+
],
32+
};
33+
34+
expect(findAdjacentSessionId(layout, "sess-left", "left")).toBeNull();
35+
expect(findAdjacentSessionId(layout, "sess-right", "right")).toBeNull();
36+
});
37+
38+
it("follows visible geometry in a 2x2 layout", () => {
39+
const layout: PaneNode = {
40+
id: "root",
41+
type: "split",
42+
direction: "horizontal",
43+
ratio: 0.5,
44+
children: [
45+
{
46+
id: "left-column",
47+
type: "split",
48+
direction: "vertical",
49+
ratio: 0.5,
50+
children: [
51+
{ id: "top-left", type: "leaf", sessionId: "sess-top-left" },
52+
{ id: "bottom-left", type: "leaf", sessionId: "sess-bottom-left" },
53+
],
54+
},
55+
{
56+
id: "right-column",
57+
type: "split",
58+
direction: "vertical",
59+
ratio: 0.5,
60+
children: [
61+
{ id: "top-right", type: "leaf", sessionId: "sess-top-right" },
62+
{ id: "bottom-right", type: "leaf", sessionId: "sess-bottom-right" },
63+
],
64+
},
65+
],
66+
};
67+
68+
expect(findAdjacentSessionId(layout, "sess-top-left", "right")).toBe("sess-top-right");
69+
expect(findAdjacentSessionId(layout, "sess-top-left", "down")).toBe("sess-bottom-left");
70+
expect(findAdjacentSessionId(layout, "sess-bottom-right", "up")).toBe("sess-top-right");
71+
expect(findAdjacentSessionId(layout, "sess-bottom-right", "left")).toBe("sess-bottom-left");
72+
});
73+
74+
it("ignores draft leaves when choosing the next session", () => {
75+
const layout: PaneNode = {
76+
id: "root",
77+
type: "split",
78+
direction: "horizontal",
79+
ratio: 1 / 3,
80+
children: [
81+
{ id: "left", type: "leaf", sessionId: "sess-left" },
82+
{
83+
id: "right-stack",
84+
type: "split",
85+
direction: "vertical",
86+
ratio: 0.5,
87+
children: [
88+
{ id: "right-top", type: "leaf" },
89+
{ id: "right-bottom", type: "leaf", sessionId: "sess-bottom-right" },
90+
],
91+
},
92+
],
93+
};
94+
95+
expect(findAdjacentSessionId(layout, "sess-left", "right")).toBe("sess-bottom-right");
96+
});
97+
98+
it("breaks ties by the smallest perpendicular center delta", () => {
99+
const layout: PaneNode = {
100+
id: "root",
101+
type: "split",
102+
direction: "horizontal",
103+
ratio: 0.4,
104+
children: [
105+
{
106+
id: "left-stack",
107+
type: "split",
108+
direction: "vertical",
109+
ratio: 0.4,
110+
children: [
111+
{
112+
id: "active-row",
113+
type: "split",
114+
direction: "horizontal",
115+
ratio: 0.5,
116+
children: [
117+
{ id: "active", type: "leaf", sessionId: "sess-active" },
118+
{ id: "draft", type: "leaf" },
119+
],
120+
},
121+
{ id: "bottom-left", type: "leaf", sessionId: "sess-bottom-left" },
122+
],
123+
},
124+
{
125+
id: "right-stack",
126+
type: "split",
127+
direction: "vertical",
128+
ratio: 0.5,
129+
children: [
130+
{ id: "top-right", type: "leaf", sessionId: "sess-top-right" },
131+
{ id: "bottom-right", type: "leaf", sessionId: "sess-bottom-right" },
132+
],
133+
},
134+
],
135+
};
136+
137+
expect(findAdjacentSessionId(layout, "sess-active", "right")).toBe("sess-top-right");
138+
});
139+
140+
it("prefers the nearest edge before perpendicular center distance", () => {
141+
const layout: PaneNode = {
142+
id: "root",
143+
type: "split",
144+
direction: "horizontal",
145+
ratio: 0.25,
146+
children: [
147+
{
148+
id: "left-stack",
149+
type: "split",
150+
direction: "vertical",
151+
ratio: 0.5,
152+
children: [
153+
{ id: "active", type: "leaf", sessionId: "sess-active" },
154+
{ id: "bottom-left", type: "leaf", sessionId: "sess-bottom-left" },
155+
],
156+
},
157+
{
158+
id: "right-region",
159+
type: "split",
160+
direction: "horizontal",
161+
ratio: 1 / 3,
162+
children: [
163+
{
164+
id: "near-column",
165+
type: "split",
166+
direction: "vertical",
167+
ratio: 0.9,
168+
children: [
169+
{ id: "near-right", type: "leaf", sessionId: "sess-near-right" },
170+
{ id: "bottom-middle", type: "leaf", sessionId: "sess-bottom-middle" },
171+
],
172+
},
173+
{
174+
id: "far-column",
175+
type: "split",
176+
direction: "vertical",
177+
ratio: 0.6,
178+
children: [
179+
{ id: "far-right", type: "leaf", sessionId: "sess-far-right" },
180+
{ id: "bottom-right", type: "leaf", sessionId: "sess-bottom-right" },
181+
],
182+
},
183+
],
184+
},
185+
],
186+
};
187+
188+
expect(findAdjacentSessionId(layout, "sess-active", "right")).toBe("sess-near-right");
189+
});
190+
191+
it("prefers an overlapping candidate over a closer non-overlapping candidate", () => {
192+
const layout: PaneNode = {
193+
id: "root",
194+
type: "split",
195+
direction: "horizontal",
196+
ratio: 0.25,
197+
children: [
198+
{
199+
id: "left-stack",
200+
type: "split",
201+
direction: "vertical",
202+
ratio: 0.5,
203+
children: [
204+
{ id: "active", type: "leaf", sessionId: "sess-active" },
205+
{ id: "bottom-left", type: "leaf", sessionId: "sess-bottom-left" },
206+
],
207+
},
208+
{
209+
id: "right-region",
210+
type: "split",
211+
direction: "horizontal",
212+
ratio: 0.2,
213+
children: [
214+
{
215+
id: "near-column",
216+
type: "split",
217+
direction: "vertical",
218+
ratio: 0.5,
219+
children: [
220+
{ id: "near-top", type: "leaf" },
221+
{ id: "near-bottom", type: "leaf", sessionId: "sess-near-bottom" },
222+
],
223+
},
224+
{
225+
id: "overlap-column",
226+
type: "split",
227+
direction: "vertical",
228+
ratio: 0.25,
229+
children: [
230+
{ id: "overlap-top", type: "leaf", sessionId: "sess-overlap-top" },
231+
{ id: "overlap-bottom", type: "leaf", sessionId: "sess-overlap-bottom" },
232+
],
233+
},
234+
],
235+
},
236+
],
237+
};
238+
239+
expect(findAdjacentSessionId(layout, "sess-active", "right")).toBe("sess-overlap-top");
240+
});
241+
});

0 commit comments

Comments
 (0)