Skip to content

Commit faffa92

Browse files
committed
test(e2e): port view-state suite with reload and rehydration coverage
Port source: CS e9a44b2 apps/vscode-e2e (A5 #929 re-review series) — 3 files byte-identical, 2 suite tests F6-authored: sidebar webview reload/rehydration test (A5 minor 2) — the durable per-view mode survives a sidebar hide/show cycle
1 parent be7b2d9 commit faffa92

4 files changed

Lines changed: 543 additions & 0 deletions

File tree

‎apps/vscode-e2e/fixtures/modes.json‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@
1313
}
1414
]
1515
}
16+
},
17+
{
18+
"match": {
19+
"userMessage": "Use the `switch_mode` tool to switch to debug mode."
20+
},
21+
"response": {
22+
"toolCalls": [
23+
{
24+
"name": "switch_mode",
25+
"arguments": "{\"mode_slug\":\"debug\",\"reason\":\"User requested to switch to debug mode.\"}",
26+
"id": "call_modes_switch_002"
27+
}
28+
]
29+
}
1630
}
1731
]
1832
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import type { ChatCompletionRequest, ChatMessage, LLMock } from "@copilotkit/aimock"
2+
3+
const TASKS = ["A", "B", "C"] as const
4+
const ROUNDS = 10
5+
6+
const MODE_SEQUENCES: Record<(typeof TASKS)[number], string[]> = {
7+
A: ["ask", "debug", "architect", "orchestrator", "code", "ask", "debug", "architect", "orchestrator", "code"],
8+
B: ["debug", "architect", "orchestrator", "code", "ask", "debug", "architect", "orchestrator", "code", "ask"],
9+
C: ["architect", "orchestrator", "code", "ask", "debug", "architect", "orchestrator", "code", "ask", "debug"],
10+
}
11+
12+
const markerFor = (taskName: (typeof TASKS)[number]) => `FOLLOWUP_MODE_ISOLATION_${taskName}`
13+
const answerFor = (taskName: (typeof TASKS)[number], round: number) => `${taskName} follow-up round ${round}`
14+
const callIdFor = (taskName: (typeof TASKS)[number], round: number) =>
15+
`call_followup_mode_${taskName.toLowerCase()}_${String(round).padStart(2, "0")}`
16+
17+
const lastToolResultContains = (req: ChatCompletionRequest, toolCallId: string, expected: string[]) => {
18+
const messages = Array.isArray(req?.messages) ? req.messages : []
19+
const toolMessage = messages.filter((message: ChatMessage) => message?.role === "tool").at(-1)
20+
const content = toolMessage?.content
21+
22+
return (
23+
toolMessage?.tool_call_id === toolCallId &&
24+
typeof content === "string" &&
25+
expected.every((text) => content.includes(text))
26+
)
27+
}
28+
29+
const followupToolCall = (taskName: (typeof TASKS)[number], round: number) => ({
30+
name: "ask_followup_question",
31+
arguments: JSON.stringify({
32+
question: `Task ${taskName}: choose mode for round ${round}`,
33+
follow_up: [
34+
{
35+
text: answerFor(taskName, round),
36+
mode: MODE_SEQUENCES[taskName][round - 1],
37+
},
38+
],
39+
}),
40+
id: callIdFor(taskName, round),
41+
})
42+
43+
export const getFollowupModeIsolationPlan = () =>
44+
TASKS.map((taskName) => ({
45+
taskName,
46+
marker: markerFor(taskName),
47+
rounds: MODE_SEQUENCES[taskName].map((mode, index) => ({
48+
round: index + 1,
49+
answer: answerFor(taskName, index + 1),
50+
mode,
51+
})),
52+
}))
53+
54+
export function addViewStateFixtures(mock: InstanceType<typeof LLMock>) {
55+
for (const taskName of TASKS) {
56+
mock.addFixture({
57+
match: {
58+
userMessage: markerFor(taskName),
59+
},
60+
response: {
61+
toolCalls: [followupToolCall(taskName, 1)],
62+
},
63+
})
64+
65+
for (let round = 1; round < ROUNDS; round++) {
66+
mock.addFixture({
67+
match: {
68+
predicate: (req) =>
69+
lastToolResultContains(req, callIdFor(taskName, round), [answerFor(taskName, round)]),
70+
},
71+
response: {
72+
toolCalls: [followupToolCall(taskName, round + 1)],
73+
},
74+
})
75+
}
76+
77+
mock.addFixture({
78+
match: {
79+
predicate: (req) =>
80+
lastToolResultContains(req, callIdFor(taskName, ROUNDS), [answerFor(taskName, ROUNDS)]),
81+
},
82+
response: {
83+
toolCalls: [
84+
{
85+
name: "attempt_completion",
86+
arguments: JSON.stringify({
87+
result: `Task ${taskName} completed ${ROUNDS} follow-up mode switches.`,
88+
}),
89+
id: `call_followup_mode_${taskName.toLowerCase()}_complete`,
90+
},
91+
],
92+
},
93+
})
94+
}
95+
}

‎apps/vscode-e2e/src/runTest.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { addUseMcpToolResultFixtures } from "./fixtures/use-mcp-tool"
2323
import { addWriteToFileResultFixtures } from "./fixtures/write-to-file"
2424
import { createScenarioWorkspace, removeScenarioWorkspace } from "./restart/scenarioWorkspace"
2525
import { runRestartScenario } from "./restart/vscodeCoordinator"
26+
import { toolResultContains } from "./fixtures/tool-result"
27+
import { addViewStateFixtures } from "./fixtures/view-state"
2628

2729
function getCliFlagValue(flag: string) {
2830
return process.argv.find((arg, index) => process.argv[index - 1] === flag)
@@ -143,6 +145,41 @@ async function main() {
143145
addUseMcpToolResultFixtures(mock)
144146
addWriteToFileResultFixtures(mock)
145147
addDeepSeekV4Fixtures(mock)
148+
addViewStateFixtures(mock)
149+
150+
// Model-agnostic predicate fixtures for the view-state suite's post-switch
151+
// turns. They coexist with the legacy model-scoped regex fixture below
152+
// (shared response id call_modes_post_switch_001) so the modes suite keeps
153+
// its OpenRouter-scoped match while view-state runs under any default model.
154+
mock.addFixture({
155+
match: {
156+
predicate: (req) => toolResultContains(req, "call_modes_switch_001", []),
157+
},
158+
response: {
159+
toolCalls: [
160+
{
161+
name: "attempt_completion",
162+
arguments: JSON.stringify({ result: "Switched to ❓ Ask mode as requested." }),
163+
id: "call_modes_post_switch_001",
164+
},
165+
],
166+
},
167+
})
168+
169+
mock.addFixture({
170+
match: {
171+
predicate: (req) => toolResultContains(req, "call_modes_switch_002", []),
172+
},
173+
response: {
174+
toolCalls: [
175+
{
176+
name: "attempt_completion",
177+
arguments: JSON.stringify({ result: "Switched to 🪲 Debug mode as requested." }),
178+
id: "call_modes_post_switch_002",
179+
},
180+
],
181+
},
182+
})
146183

147184
// The modes test (switch_mode → ask) triggers a second API call whose last
148185
// user message starts with <environment_details> directly — no <user_message>

0 commit comments

Comments
 (0)