forked from different-ai/openwork
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnew-split-session.e2e.test.ts
More file actions
171 lines (159 loc) · 8.32 KB
/
Copy pathnew-split-session.e2e.test.ts
File metadata and controls
171 lines (159 loc) · 8.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import { expect } from "vitest";
import { spec } from "@openwork/testkit";
import { newSplitPrimary } from "../worlds/chat.ts";
const test = spec.world(newSplitPrimary);
const paletteInput = { placeholder: "Search actions, settings, and sessions…" };
type SplitFacts = {
layoutKind: string;
primarySessionId: string;
secondarySessionId: string;
primaryWorkspaceId: string;
secondaryWorkspaceId: string;
primarySurfaceSessionId: string;
secondarySurfaceSessionId: string;
secondaryPaneWorkspaceId: string;
secondaryPaneCount: number;
locationHash: string;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function parseSplitFacts(value: unknown): SplitFacts {
if (!isRecord(value)) throw new Error(`Invalid split facts: ${JSON.stringify(value)}`);
const text = (key: string) => typeof value[key] === "string" ? value[key] : "";
return {
layoutKind: text("layoutKind"),
primarySessionId: text("primarySessionId"),
secondarySessionId: text("secondarySessionId"),
primaryWorkspaceId: text("primaryWorkspaceId"),
secondaryWorkspaceId: text("secondaryWorkspaceId"),
primarySurfaceSessionId: text("primarySurfaceSessionId"),
secondarySurfaceSessionId: text("secondarySurfaceSessionId"),
secondaryPaneWorkspaceId: text("secondaryPaneWorkspaceId"),
secondaryPaneCount: typeof value.secondaryPaneCount === "number" ? value.secondaryPaneCount : -1,
locationHash: text("locationHash"),
};
}
test("new split creates fresh same-workspace secondary sessions without moving the primary", async ({ world, user, agent, probe, step, place }) => {
// The key chord belongs to the machine running the app, not the one running the spec.
const paletteShortcut = place.kind !== "daytona" && process.platform === "darwin" ? "Meta+K" : "Control+K";
const workspaceId = world.workspace.workspaceId;
const primarySessionId = world.session.sessionId;
const { primaryHash, before } = await step("the seeded session is the single primary pane", async () => {
await probe.eventually(() => world.splitFacts(), {
within: 60_000,
label: "single layout on the seeded primary",
until: (value) => {
const facts = parseSplitFacts(value);
return facts.layoutKind === "single" && facts.primarySessionId === primarySessionId;
},
});
return { primaryHash: await probe.hash(), before: await agent.list() };
});
const beforeIds = before.map((session) => session.sessionId);
await step("New split in the session context menu opens a fresh secondary", async () => {
// The sidebar row is the first place the title renders; the pane header comes later in DOM order.
await user.rightClick({ text: world.session.title });
await user.see({ role: "menuitem", label: "New split" });
await user.click({ role: "menuitem", label: "New split" });
await user.see({ text: "Split view" });
await user.see({ text: "New session" });
});
const { firstFacts, afterContextMenu } = await step("the context menu creates one same-workspace split session", async () => {
const firstValue = await probe.eventually(() => world.splitFacts(), {
within: 60_000,
label: "context-menu new split layout",
until: (value) => {
const facts = parseSplitFacts(value);
return facts.layoutKind === "split"
&& facts.primarySessionId === primarySessionId
&& facts.primaryWorkspaceId === workspaceId
&& facts.primarySurfaceSessionId === primarySessionId
&& /^ses_/.test(facts.secondarySessionId)
&& !beforeIds.includes(facts.secondarySessionId)
&& facts.secondaryWorkspaceId === facts.primaryWorkspaceId
&& facts.secondarySurfaceSessionId === facts.secondarySessionId
&& facts.secondaryPaneWorkspaceId === workspaceId
&& facts.secondaryPaneCount === 1
&& facts.locationHash === primaryHash;
},
});
const firstFacts = parseSplitFacts(firstValue);
expect(firstFacts.layoutKind).toBe("split");
expect(firstFacts.primarySessionId).toBe(primarySessionId);
expect(firstFacts.primaryWorkspaceId).toBe(workspaceId);
expect(firstFacts.primarySurfaceSessionId).toBe(primarySessionId);
expect(firstFacts.secondarySessionId).toMatch(/^ses_/);
expect(beforeIds).not.toContain(firstFacts.secondarySessionId);
expect(firstFacts.secondaryWorkspaceId).toBe(firstFacts.primaryWorkspaceId);
expect(firstFacts.secondarySurfaceSessionId).toBe(firstFacts.secondarySessionId);
expect(firstFacts.secondaryPaneWorkspaceId).toBe(workspaceId);
expect(firstFacts.secondaryPaneCount).toBe(1);
expect(firstFacts.locationHash).toBe(primaryHash);
expect(await probe.hash()).toContain(`/session/${primarySessionId}`);
const afterContextMenu = await probe.eventually(() => agent.list(), {
within: 60_000,
label: "context-menu split session appears in the session list",
until: (sessions) => sessions.length === before.length + 1
&& sessions.some((session) => session.sessionId === firstFacts.secondarySessionId),
});
expect(afterContextMenu).toHaveLength(before.length + 1);
expect(afterContextMenu.map((session) => session.sessionId)).toContain(firstFacts.secondarySessionId);
await user.screenshot();
return { firstFacts, afterContextMenu };
});
await step("New split in the command palette replaces the secondary with another fresh session", async () => {
await user.press(paletteShortcut);
await user.see(paletteInput);
await user.type(paletteInput, "new split", { replace: true });
await user.see({ role: "option", label: /^New split/ });
await user.press("Enter");
await user.see({ text: "Split view" });
await user.see({ text: "New session" });
});
await step("the palette creates one more session while preserving the primary route", async () => {
const afterContextMenuIds = afterContextMenu.map((session) => session.sessionId);
const secondValue = await probe.eventually(() => world.splitFacts(), {
within: 60_000,
label: "palette new split replaces the secondary session",
until: (value) => {
const facts = parseSplitFacts(value);
return facts.layoutKind === "split"
&& facts.primarySessionId === primarySessionId
&& facts.primaryWorkspaceId === workspaceId
&& facts.primarySurfaceSessionId === primarySessionId
&& /^ses_/.test(facts.secondarySessionId)
&& facts.secondarySessionId !== firstFacts.secondarySessionId
&& !afterContextMenuIds.includes(facts.secondarySessionId)
&& facts.secondaryWorkspaceId === facts.primaryWorkspaceId
&& facts.secondarySurfaceSessionId === facts.secondarySessionId
&& facts.secondaryPaneWorkspaceId === workspaceId
&& facts.secondaryPaneCount === 1
&& facts.locationHash === primaryHash;
},
});
const secondFacts = parseSplitFacts(secondValue);
expect(secondFacts.layoutKind).toBe("split");
expect(secondFacts.primarySessionId).toBe(primarySessionId);
expect(secondFacts.primaryWorkspaceId).toBe(workspaceId);
expect(secondFacts.primarySurfaceSessionId).toBe(primarySessionId);
expect(secondFacts.secondarySessionId).toMatch(/^ses_/);
expect(secondFacts.secondarySessionId).not.toBe(firstFacts.secondarySessionId);
expect(afterContextMenuIds).not.toContain(secondFacts.secondarySessionId);
expect(secondFacts.secondaryWorkspaceId).toBe(secondFacts.primaryWorkspaceId);
expect(secondFacts.secondarySurfaceSessionId).toBe(secondFacts.secondarySessionId);
expect(secondFacts.secondaryPaneWorkspaceId).toBe(workspaceId);
expect(secondFacts.secondaryPaneCount).toBe(1);
expect(secondFacts.locationHash).toBe(primaryHash);
expect(await probe.hash()).toContain(`/session/${primarySessionId}`);
const afterPalette = await probe.eventually(() => agent.list(), {
within: 60_000,
label: "palette split session appears in the session list",
until: (sessions) => sessions.length === before.length + 2
&& sessions.some((session) => session.sessionId === secondFacts.secondarySessionId),
});
expect(afterPalette).toHaveLength(before.length + 2);
expect(afterPalette.map((session) => session.sessionId)).toContain(secondFacts.secondarySessionId);
await user.screenshot();
});
});