Skip to content

Commit 3a46a09

Browse files
committed
test(webview): restore ClineProvider parallel mode coverage
1 parent a14bbc1 commit 3a46a09

1 file changed

Lines changed: 165 additions & 0 deletions

File tree

src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,22 @@ describe("ClineProvider - Parallel Mode Support", () => {
616616
dispose: vi.fn(),
617617
} as unknown as vscode.OutputChannel
618618
})
619+
620+
const createMockWebviewView = (postMessage = vi.fn()) =>
621+
({
622+
webview: {
623+
postMessage,
624+
html: "",
625+
options: {},
626+
onDidReceiveMessage: vi.fn(),
627+
asWebviewUri: vi.fn(),
628+
cspSource: "vscode-webview://test-csp-source",
629+
},
630+
visible: true,
631+
onDidChangeVisibility: vi.fn(() => ({ dispose: vi.fn() })),
632+
onDidDispose: vi.fn(() => ({ dispose: vi.fn() })),
633+
}) as any
634+
619635
describe("viewId uniqueness", () => {
620636
it("should assign unique viewId to each instance", async () => {
621637
const provider1 = new ClineProvider(
@@ -1022,6 +1038,155 @@ describe("ClineProvider - Parallel Mode Support", () => {
10221038
})
10231039
})
10241040

1041+
describe("handleModeSwitch integration", () => {
1042+
it("should update viewLocalState.mode when handleModeSwitch is called", async () => {
1043+
const postMessage = vi.fn()
1044+
const provider = new ClineProvider(mockContext, mockOutputChannel, "sidebar", new ContextProxy(mockContext))
1045+
1046+
await (provider as any).resolveWebviewView(createMockWebviewView(postMessage))
1047+
1048+
const saveViewStateSpy = vi.spyOn(provider as any, "saveViewState")
1049+
1050+
await provider.handleModeSwitch("architect" as any)
1051+
1052+
expect((provider as any).viewLocalState.mode).toBe("architect")
1053+
expect(saveViewStateSpy).toHaveBeenCalledWith("mode", "architect")
1054+
1055+
await provider.dispose()
1056+
})
1057+
1058+
it("should post state and skip mode config lookup when API config locking is enabled", async () => {
1059+
const postMessage = vi.fn()
1060+
mockContext.workspaceState.get = vi.fn().mockImplementation((key: string, fallback?: unknown) => {
1061+
return key === "lockApiConfigAcrossModes" ? true : fallback
1062+
})
1063+
1064+
const provider = new ClineProvider(mockContext, mockOutputChannel, "sidebar", new ContextProxy(mockContext))
1065+
const getModeConfigIdSpy = vi.spyOn(provider.providerSettingsManager, "getModeConfigId")
1066+
1067+
await (provider as any).resolveWebviewView(createMockWebviewView(postMessage))
1068+
postMessage.mockClear()
1069+
1070+
await provider.handleModeSwitch("architect" as any)
1071+
1072+
expect(getModeConfigIdSpy).not.toHaveBeenCalled()
1073+
expect(postMessage).toHaveBeenCalled()
1074+
1075+
await provider.dispose()
1076+
})
1077+
1078+
it("should activate configured mode profile when switching modes", async () => {
1079+
const provider = new ClineProvider(mockContext, mockOutputChannel, "sidebar", new ContextProxy(mockContext))
1080+
vi.spyOn(provider.providerSettingsManager, "getModeConfigId").mockResolvedValueOnce("profile-id")
1081+
vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValueOnce([
1082+
{ id: "profile-id", name: "mode-profile", apiProvider: "openrouter" },
1083+
] as any)
1084+
vi.spyOn(provider.providerSettingsManager, "getProfile").mockResolvedValueOnce({
1085+
apiProvider: "openrouter",
1086+
} as any)
1087+
const activateProviderProfileSpy = vi.spyOn(provider, "activateProviderProfile")
1088+
1089+
await provider.handleModeSwitch("architect" as any)
1090+
1091+
expect(activateProviderProfileSpy).toHaveBeenCalledWith({ name: "mode-profile" })
1092+
1093+
await provider.dispose()
1094+
})
1095+
1096+
it("should leave current configuration unchanged for empty mode profiles", async () => {
1097+
const provider = new ClineProvider(mockContext, mockOutputChannel, "sidebar", new ContextProxy(mockContext))
1098+
vi.spyOn(provider.providerSettingsManager, "getModeConfigId").mockResolvedValueOnce("empty-profile-id")
1099+
vi.spyOn(provider.providerSettingsManager, "listConfig").mockResolvedValueOnce([
1100+
{ id: "empty-profile-id", name: "empty-profile" },
1101+
] as any)
1102+
vi.spyOn(provider.providerSettingsManager, "getProfile").mockResolvedValueOnce({} as any)
1103+
const activateProviderProfileSpy = vi.spyOn(provider, "activateProviderProfile")
1104+
1105+
await provider.handleModeSwitch("architect" as any)
1106+
1107+
expect(activateProviderProfileSpy).not.toHaveBeenCalled()
1108+
1109+
await provider.dispose()
1110+
})
1111+
1112+
it("should emit ModeChanged event after handleModeSwitch", async () => {
1113+
const provider = new ClineProvider(mockContext, mockOutputChannel, "sidebar", new ContextProxy(mockContext))
1114+
const modeChangedSpy = vi.fn()
1115+
1116+
provider.on(RooCodeEventName.ModeChanged, modeChangedSpy)
1117+
1118+
await provider.handleModeSwitch("architect" as any)
1119+
1120+
expect(modeChangedSpy).toHaveBeenCalledWith("architect")
1121+
1122+
await provider.dispose()
1123+
})
1124+
})
1125+
1126+
describe("multi-instance isolation", () => {
1127+
it("should maintain independent state across three instances", async () => {
1128+
const provider1 = new ClineProvider(
1129+
mockContext,
1130+
mockOutputChannel,
1131+
"sidebar",
1132+
new ContextProxy(mockContext),
1133+
)
1134+
const provider2 = new ClineProvider(mockContext, mockOutputChannel, "editor", new ContextProxy(mockContext))
1135+
const provider3 = new ClineProvider(mockContext, mockOutputChannel, "editor", new ContextProxy(mockContext))
1136+
1137+
await (provider1 as any).saveViewState("mode", "code")
1138+
await (provider1 as any).saveViewState("currentApiConfigName", "profile-1")
1139+
await (provider2 as any).saveViewState("mode", "architect")
1140+
await (provider2 as any).saveViewState("currentApiConfigName", "profile-2")
1141+
await (provider3 as any).saveViewState("mode", "debugger")
1142+
await (provider3 as any).saveViewState("currentApiConfigName", "profile-3")
1143+
1144+
const state1 = await provider1.getState()
1145+
const state2 = await provider2.getState()
1146+
const state3 = await provider3.getState()
1147+
1148+
expect(state1.mode).toBe("code")
1149+
expect(state1.currentApiConfigName).toBe("profile-1")
1150+
expect(state2.mode).toBe("architect")
1151+
expect(state2.currentApiConfigName).toBe("profile-2")
1152+
expect(state3.mode).toBe("debugger")
1153+
expect(state3.currentApiConfigName).toBe("profile-3")
1154+
1155+
await provider1.dispose()
1156+
await provider2.dispose()
1157+
await provider3.dispose()
1158+
})
1159+
1160+
it("should handle mode switch in one instance without affecting others", async () => {
1161+
const postMessage1 = vi.fn()
1162+
const postMessage2 = vi.fn()
1163+
const provider1 = new ClineProvider(
1164+
mockContext,
1165+
mockOutputChannel,
1166+
"sidebar",
1167+
new ContextProxy(mockContext),
1168+
)
1169+
const provider2 = new ClineProvider(mockContext, mockOutputChannel, "editor", new ContextProxy(mockContext))
1170+
1171+
await (provider1 as any).resolveWebviewView(createMockWebviewView(postMessage1))
1172+
await (provider2 as any).resolveWebviewView(createMockWebviewView(postMessage2))
1173+
await (provider1 as any).saveViewState("mode", "code")
1174+
await (provider2 as any).saveViewState("mode", "debugger")
1175+
1176+
await provider1.handleModeSwitch("architect" as any)
1177+
1178+
const state1 = await provider1.getState()
1179+
const state2 = await provider2.getState()
1180+
1181+
expect(state1.mode).toBe("architect")
1182+
expect(state2.mode).toBe("debugger")
1183+
expect((provider2 as any).viewLocalState.mode).toBe("debugger")
1184+
1185+
await provider1.dispose()
1186+
await provider2.dispose()
1187+
})
1188+
})
1189+
10251190
describe("_clearViewLocalState", () => {
10261191
it("should clear all view-local state values", async () => {
10271192
const provider = new ClineProvider(mockContext, mockOutputChannel, "sidebar", new ContextProxy(mockContext))

0 commit comments

Comments
 (0)