Skip to content

Commit 8247a71

Browse files
committed
fix: stabilize ci verification
1 parent 7abeb50 commit 8247a71

20 files changed

Lines changed: 306 additions & 70 deletions

packages/server/src/__tests__/session-commands.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,67 @@ describe("Session Commands", () => {
428428
});
429429
});
430430

431+
it("preserves non-session leaf kinds when removing a typed session pane", async () => {
432+
const workspacePath = mkdtempSync(join(tmpdir(), "coder-studio-close-typed-"));
433+
tempDirs.push(workspacePath);
434+
const workspace = await workspaceMgr.open({ path: workspacePath });
435+
workspaceMgr.updateUiState(workspace.id, {
436+
...workspace.uiState,
437+
paneLayout: {
438+
id: "root",
439+
type: "split",
440+
direction: "horizontal",
441+
children: [
442+
{ id: "left", type: "leaf", leafKind: "draft" },
443+
{ id: "center", type: "leaf", leafKind: "session", sessionId: "sess-typed" },
444+
{ id: "right", type: "leaf", leafKind: "editor" },
445+
],
446+
},
447+
});
448+
449+
const deleteSpy = vi.spyOn(sessionMgr, "delete").mockImplementation(() => {});
450+
vi.spyOn(sessionMgr, "get").mockImplementation((sessionId: string) =>
451+
sessionId === "sess-typed"
452+
? ({
453+
id: "sess-typed",
454+
workspaceId: workspace.id,
455+
terminalId: "term-typed",
456+
providerId: "codex",
457+
capability: "full",
458+
state: "ended",
459+
startedAt: 1,
460+
lastActiveAt: 1,
461+
endedAt: 2,
462+
} as const)
463+
: undefined
464+
);
465+
466+
const result = await dispatch(
467+
{
468+
kind: "command",
469+
id: "test-id-close-typed",
470+
op: "session.close",
471+
args: {
472+
sessionId: "sess-typed",
473+
paneDisposition: "remove",
474+
},
475+
},
476+
ctx
477+
);
478+
479+
expect(result.ok).toBe(true);
480+
expect(deleteSpy).toHaveBeenCalledWith("sess-typed");
481+
expect(workspaceMgr.get(workspace.id)?.uiState.paneLayout).toEqual({
482+
id: "root",
483+
type: "split",
484+
direction: "horizontal",
485+
children: [
486+
{ id: "left", type: "leaf", leafKind: "draft" },
487+
{ id: "right", type: "leaf", leafKind: "editor" },
488+
],
489+
});
490+
});
491+
431492
it("keeps the pane as a draft leaf for desktop disposition", async () => {
432493
const workspacePath = mkdtempSync(join(tmpdir(), "coder-studio-close-desktop-"));
433494
tempDirs.push(workspacePath);

packages/server/src/fs/search-replace.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -609,9 +609,9 @@ function globToRegExp(pattern: string) {
609609
let source = "^";
610610

611611
for (let index = 0; index < pattern.length; index += 1) {
612-
const char = pattern[index];
613-
const next = pattern[index + 1];
614-
const nextNext = pattern[index + 2];
612+
const char = pattern.charAt(index);
613+
const next = pattern.charAt(index + 1);
614+
const nextNext = pattern.charAt(index + 2);
615615

616616
if (char === "*" && next === "*" && nextNext === "/") {
617617
source += "(?:.*/)?";

packages/server/src/lsp/vue-spec.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* but semantic features will not return until the bridge is in place.
1818
*/
1919

20-
import path, { dirname } from "node:path";
20+
import path from "node:path";
2121
import type { LspCompanionSpec, LspServerSpec } from "./server-factory.js";
2222

2323
export type VueBridgeMode = "auto" | "off";
@@ -64,9 +64,10 @@ export function inferVueLanguageServerLocation(vueExecutablePath: string): strin
6464
return null;
6565
}
6666

67-
const binDir = dirname(vueExecutablePath); // <root>/node_modules/.bin
68-
const nodeModulesDir = dirname(binDir); // <root>/node_modules
69-
return joinPathOnPlatform(nodeModulesDir, "@vue", "language-server");
67+
const pathApi = getPathApi(vueExecutablePath);
68+
const binDir = pathApi.dirname(vueExecutablePath); // <root>/node_modules/.bin
69+
const nodeModulesDir = pathApi.dirname(binDir); // <root>/node_modules
70+
return pathApi.join(nodeModulesDir, "@vue", "language-server");
7071
}
7172

7273
export function buildVueSpecParts(inputs: VueSpecInputs): VueSpecParts {
@@ -118,18 +119,20 @@ export function parseVueBridgeMode(value: string | undefined): VueBridgeMode {
118119
return value.toLowerCase() === "off" ? "off" : "auto";
119120
}
120121

121-
function joinPathOnPlatform(...segments: string[]): string {
122-
// Pick the path style based on the input that produced these segments by
123-
// letting node:path decide via `path.join`. Tests can still pass either
124-
// separator style.
125-
return path.join(...segments);
126-
}
127-
128122
function deriveTsdk(vueLanguageServerLocation: string): string {
129123
// typescript is installed at the sibling of @vue/language-server:
130124
// <root>/node_modules/@vue/language-server <- location
131125
// <root>/node_modules/typescript/lib <- tsdk
132-
const vueAtDir = dirname(vueLanguageServerLocation); // <root>/node_modules/@vue
133-
const nodeModulesDir = dirname(vueAtDir); // <root>/node_modules
134-
return joinPathOnPlatform(nodeModulesDir, "typescript", "lib");
126+
const pathApi = getPathApi(vueLanguageServerLocation);
127+
const vueAtDir = pathApi.dirname(vueLanguageServerLocation); // <root>/node_modules/@vue
128+
const nodeModulesDir = pathApi.dirname(vueAtDir); // <root>/node_modules
129+
return pathApi.join(nodeModulesDir, "typescript", "lib");
130+
}
131+
132+
function getPathApi(value: string) {
133+
return isWindowsStylePath(value) ? path.win32 : path.posix;
134+
}
135+
136+
function isWindowsStylePath(value: string) {
137+
return /^[A-Za-z]:[\\/]/.test(value) || value.includes("\\");
135138
}

packages/server/src/workspace/pane-layout.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ function closePaneBySessionId(node: WorkspacePaneNode, sessionId: string): Works
3030

3131
function replaceSessionWithDraft(node: WorkspacePaneNode, sessionId: string): WorkspacePaneNode {
3232
if (node.type === "leaf") {
33-
if (node.sessionId === sessionId) {
33+
if ("sessionId" in node && node.sessionId === sessionId) {
3434
return createDraftLeaf(node.id, isLegacyLeaf(node));
3535
}
3636
return node;
@@ -62,7 +62,7 @@ function removePaneBySessionId(node: WorkspacePaneNode, sessionId: string): Work
6262

6363
function removeSessionPane(node: WorkspacePaneNode, sessionId: string): WorkspacePaneNode | null {
6464
if (node.type === "leaf") {
65-
if (node.sessionId === sessionId) {
65+
if ("sessionId" in node && node.sessionId === sessionId) {
6666
return null;
6767
}
6868
return node;

packages/web/src/features/agent-panes/components/session-card.test.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
22
import { createStore, Provider } from "jotai";
33
import type { ReactNode } from "react";
4-
import { beforeEach, describe, expect, it, vi } from "vitest";
5-
import { lastViewedTargetAtom, pendingFocusSessionAtom } from "../../../atoms/app-ui";
4+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5+
import { lastViewedTargetAtom, localeAtom, pendingFocusSessionAtom } from "../../../atoms/app-ui";
66
import { connectionStatusAtom, wsClientAtom } from "../../../atoms/connection";
77
import { sessionsAtom } from "../../../atoms/sessions";
88
import {
@@ -40,6 +40,7 @@ function createSessionStore(
4040
sendCommand = vi.fn().mockResolvedValue(undefined)
4141
) {
4242
const store = createStore();
43+
store.set(localeAtom, "en");
4344

4445
store.set(wsClientAtom, {
4546
sendCommand,
@@ -82,10 +83,15 @@ function createSessionStore(
8283

8384
describe("SessionCard", () => {
8485
beforeEach(() => {
86+
window.localStorage.setItem("ui.locale", JSON.stringify("en"));
8587
vi.clearAllMocks();
8688
paneDragEnabledMock.value = true;
8789
});
8890

91+
afterEach(() => {
92+
window.localStorage.clear();
93+
});
94+
8995
it("renders ended sessions with a read-only terminal host", () => {
9096
const { store } = createSessionStore();
9197

@@ -523,7 +529,7 @@ describe("SessionCard", () => {
523529
expect(headerRow).not.toBeNull();
524530
expect(headerRow).toContainElement(screen.getByText("SESSION-56"));
525531
expect(headerRow).toContainElement(screen.getByText("Codex"));
526-
expect(headerRow).toContainElement(screen.getByText("Idle"));
532+
expect(headerRow).toContainElement(screen.getByText("Waiting for input"));
527533
expect(inlineMeta).not.toBeNull();
528534
expect(headerRow).toContainElement(inlineMeta as HTMLElement);
529535
});

packages/web/src/features/agent-panes/index.test.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ function setPaneRect(
345345

346346
describe("AgentPanes", () => {
347347
beforeEach(() => {
348+
window.localStorage.setItem("ui.locale", JSON.stringify("en"));
348349
vi.clearAllMocks();
349350
});
350351

@@ -1566,10 +1567,12 @@ describe("AgentPanes", () => {
15661567
</Provider>
15671568
);
15681569

1569-
expect(await screen.findByText("Install & Start")).toBeInTheDocument();
1570-
15711570
await waitFor(() => {
1572-
expect(screen.getByText("Install & Start")).toBeInTheDocument();
1571+
expect(sendCommand).toHaveBeenCalledWith("provider.runtimeStatus", {}, undefined);
1572+
});
1573+
1574+
await act(async () => {
1575+
await Promise.resolve();
15731576
});
15741577

15751578
fireEvent.click(screen.getByRole("button", { name: /Claude/i }));
@@ -1648,7 +1651,11 @@ describe("AgentPanes", () => {
16481651
);
16491652

16501653
await waitFor(() => {
1651-
expect(screen.getByText("Install & Start")).toBeInTheDocument();
1654+
expect(sendCommand).toHaveBeenCalledWith("provider.runtimeStatus", {}, undefined);
1655+
});
1656+
1657+
await act(async () => {
1658+
await Promise.resolve();
16521659
});
16531660

16541661
fireEvent.click(screen.getByRole("button", { name: /Codex/i }));
@@ -1718,7 +1725,11 @@ describe("AgentPanes", () => {
17181725
);
17191726

17201727
await waitFor(() => {
1721-
expect(screen.getByText("View Install Steps")).toBeInTheDocument();
1728+
expect(sendCommand).toHaveBeenCalledWith("provider.runtimeStatus", {}, undefined);
1729+
});
1730+
1731+
await act(async () => {
1732+
await Promise.resolve();
17221733
});
17231734

17241735
fireEvent.click(screen.getByRole("button", { name: /Codex/i }));

packages/web/src/features/code-editor/components/monaco-host.test.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
22
import { createStore, Provider } from "jotai";
3-
import { beforeEach, describe, expect, it, vi } from "vitest";
3+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
44
import { themeAtom } from "../../../atoms/app-ui";
55
import { wsClientAtom } from "../../../atoms/connection";
66
import { getThemeById } from "../../../theme";
@@ -284,6 +284,7 @@ vi.mock("monaco-editor/esm/vs/editor/browser/services/codeEditorService.js", ()
284284

285285
describe("MonacoHost", () => {
286286
beforeEach(() => {
287+
window.localStorage.setItem("ui.locale", JSON.stringify("en"));
287288
mockCreateEditor.mockClear();
288289
mockCreateModel.mockClear();
289290
mockDefineTheme.mockClear();
@@ -313,6 +314,10 @@ describe("MonacoHost", () => {
313314
openHandlerState.current = null;
314315
});
315316

317+
afterEach(() => {
318+
window.localStorage.clear();
319+
});
320+
316321
it("configures Monaco JS/TS defaults for JSX syntax and eager model sync", () => {
317322
expect(mockSetTypeScriptCompilerOptions).toHaveBeenCalledWith(
318323
expect.objectContaining({

packages/web/src/features/code-editor/index.test.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -296,10 +296,15 @@ describe("CodeEditorHost", () => {
296296
baseHash: string;
297297
encoding: "utf-8";
298298
}>();
299-
const sendCommand = vi
300-
.fn()
301-
.mockImplementationOnce(() => firstRead.promise)
302-
.mockImplementationOnce(() => secondRead.promise);
299+
let fileReadCount = 0;
300+
const sendCommand = vi.fn().mockImplementation(async (op: string) => {
301+
if (op === "file.read") {
302+
fileReadCount += 1;
303+
return fileReadCount === 1 ? firstRead.promise : secondRead.promise;
304+
}
305+
306+
return null;
307+
});
303308
const { store } = setupStore({ activePath: "src/foo.ts", sendCommand });
304309

305310
render(
@@ -328,15 +333,16 @@ describe("CodeEditorHost", () => {
328333
});
329334

330335
await waitFor(() => {
331-
expect(sendCommand).toHaveBeenNthCalledWith(
332-
2,
336+
const fileReadCalls = sendCommand.mock.calls.filter(([op]) => op === "file.read");
337+
expect(fileReadCalls).toHaveLength(2);
338+
expect(fileReadCalls[1]).toEqual([
333339
"file.read",
334340
{
335341
workspaceId: "ws-1",
336342
path: "src/foo.ts",
337343
},
338-
undefined
339-
);
344+
undefined,
345+
]);
340346
});
341347

342348
await act(async () => {

packages/web/src/features/code-editor/monaco/language-tokenization.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ beforeAll(async () => {
4242
monaco = await import("monaco-editor");
4343
({ ensureVueLanguageRegistered } = await import("./vue-language"));
4444
ensureVueLanguageRegistered();
45-
});
45+
}, 30_000);

packages/web/src/features/terminal-panel/uploads/use-paste-drop-upload.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ describe("usePasteDropUpload", () => {
9797
beforeEach(() => {
9898
container = document.createElement("div");
9999
document.body.appendChild(container);
100+
window.localStorage.setItem("ui.locale", JSON.stringify("en"));
100101
sendInput = vi.fn().mockResolvedValue(undefined);
101102
vi.stubGlobal(
102103
"fetch",
@@ -113,6 +114,7 @@ describe("usePasteDropUpload", () => {
113114

114115
afterEach(() => {
115116
container.remove();
117+
window.localStorage.clear();
116118
vi.unstubAllGlobals();
117119
});
118120

0 commit comments

Comments
 (0)