| @@ -356,6 +356,25 @@ function withProjectScripts( · 355 unmodified lines |
| 356 | 356 | }; |
| 357 | 357 | } |
| 358 | 358 | |
| 359 | +function setDraftThreadWithoutWorktree(): void { |
| 360 | + useComposerDraftStore.setState({ |
| 361 | + draftThreadsByThreadId: { |
| 362 | + [THREAD_ID]: { |
| 363 | + projectId: PROJECT_ID, |
| 364 | + createdAt: NOW_ISO, |
| 365 | + runtimeMode: "full-access", |
| 366 | + interactionMode: "default", |
| 367 | + branch: null, |
| 368 | + worktreePath: null, |
| 369 | + envMode: "local", |
| 370 | + }, |
| 371 | + }, |
| 372 | + projectDraftThreadIdByProjectId: { |
| 373 | + [PROJECT_ID]: THREAD_ID, |
| 374 | + }, |
| 375 | + }); |
| 376 | +} |
| 377 | + |
| 359 | 378 | function createSnapshotWithLongProposedPlan(): OrchestrationReadModel { |
| 360 | 379 | const snapshot = createSnapshotForTargetUser({ |
| 361 | 380 | targetMessageId: "msg-user-plan-target" as MessageId, |
| @@ -1011,30 +1030,162 @@ describe("ChatView timeline estimator parity (full app)", () => { · 649 unmodified lines |
| 1011 | 1030 | ); |
| 1012 | 1031 | |
| 1013 | 1032 | it("opens the project cwd for draft threads without a worktree path", async () => { |
| 1014 | | − useComposerDraftStore.setState({ |
| 1015 | | − draftThreadsByThreadId: { |
| 1016 | | − [THREAD_ID]: { |
| 1017 | | − projectId: PROJECT_ID, |
| 1018 | | − createdAt: NOW_ISO, |
| 1019 | | − runtimeMode: "full-access", |
| 1020 | | − interactionMode: "default", |
| 1021 | | − branch: null, |
| 1022 | | − worktreePath: null, |
| 1023 | | − envMode: "local", |
| 1033 | + setDraftThreadWithoutWorktree(); |
| 1034 | + |
| 1035 | + const mounted = await mountChatView({ |
| 1036 | + viewport: DEFAULT_VIEWPORT, |
| 1037 | + snapshot: createDraftOnlySnapshot(), |
| 1038 | + configureFixture: (nextFixture) => { |
| 1039 | + nextFixture.serverConfig = { |
| 1040 | + ...nextFixture.serverConfig, |
| 1041 | + availableEditors: ["vscode"], |
| 1042 | + }; |
| 1043 | + }, |
| 1044 | + }); |
| 1045 | + |
| 1046 | + try { |
| 1047 | + const openButton = await waitForElement( |
| 1048 | + () => |
| 1049 | + Array.from(document.querySelectorAll("button")).find( |
| 1050 | + (button) => button.textContent?.trim() === "Open", |
| 1051 | + ) as HTMLButtonElement | null, |
| 1052 | + "Unable to find Open button.", |
| 1053 | + ); |
| 1054 | + openButton.click(); |
| 1055 | + |
| 1056 | + await vi.waitFor( |
| 1057 | + () => { |
| 1058 | + const openRequest = wsRequests.find( |
| 1059 | + (request) => request._tag === WS_METHODS.shellOpenInEditor, |
| 1060 | + ); |
| 1061 | + expect(openRequest).toMatchObject({ |
| 1062 | + _tag: WS_METHODS.shellOpenInEditor, |
| 1063 | + cwd: "/repo/project", |
| 1064 | + editor: "vscode", |
| 1065 | + }); |
| 1024 | 1066 | }, |
| 1067 | + { timeout: 8_000, interval: 16 }, |
| 1068 | + ); |
| 1069 | + } finally { |
| 1070 | + await mounted.cleanup(); |
| 1071 | + } |
| 1072 | + }); |
| 1073 | + |
| 1074 | + it("opens the project cwd with VS Code Insiders when it is the only available editor", async () => { |
| 1075 | + setDraftThreadWithoutWorktree(); |
| 1076 | + |
| 1077 | + const mounted = await mountChatView({ |
| 1078 | + viewport: DEFAULT_VIEWPORT, |
| 1079 | + snapshot: createDraftOnlySnapshot(), |
| 1080 | + configureFixture: (nextFixture) => { |
| 1081 | + nextFixture.serverConfig = { |
| 1082 | + ...nextFixture.serverConfig, |
| 1083 | + availableEditors: ["vscode-insiders"], |
| 1084 | + }; |
| 1025 | 1085 | }, |
| 1026 | | − projectDraftThreadIdByProjectId: { |
| 1027 | | − [PROJECT_ID]: THREAD_ID, |
| 1086 | + }); |
| 1087 | + |
| 1088 | + try { |
| 1089 | + const openButton = await waitForElement( |
| 1090 | + () => |
| 1091 | + Array.from(document.querySelectorAll("button")).find( |
| 1092 | + (button) => button.textContent?.trim() === "Open", |
| 1093 | + ) as HTMLButtonElement | null, |
| 1094 | + "Unable to find Open button.", |
| 1095 | + ); |
| 1096 | + openButton.click(); |
| 1097 | + |
| 1098 | + await vi.waitFor( |
| 1099 | + () => { |
| 1100 | + const openRequest = wsRequests.find( |
| 1101 | + (request) => request._tag === WS_METHODS.shellOpenInEditor, |
| 1102 | + ); |
| 1103 | + expect(openRequest).toMatchObject({ |
| 1104 | + _tag: WS_METHODS.shellOpenInEditor, |
| 1105 | + cwd: "/repo/project", |
| 1106 | + editor: "vscode-insiders", |
| 1107 | + }); |
| 1108 | + }, |
| 1109 | + { timeout: 8_000, interval: 16 }, |
| 1110 | + ); |
| 1111 | + } finally { |
| 1112 | + await mounted.cleanup(); |
| 1113 | + } |
| 1114 | + }); |
| 1115 | + |
| 1116 | + it("filters the open picker menu and opens VSCodium from the menu", async () => { |
| 1117 | + setDraftThreadWithoutWorktree(); |
| 1118 | + |
| 1119 | + const mounted = await mountChatView({ |
| 1120 | + viewport: DEFAULT_VIEWPORT, |
| 1121 | + snapshot: createDraftOnlySnapshot(), |
| 1122 | + configureFixture: (nextFixture) => { |
| 1123 | + nextFixture.serverConfig = { |
| 1124 | + ...nextFixture.serverConfig, |
| 1125 | + availableEditors: ["vscode-insiders", "vscodium"], |
| 1126 | + }; |
| 1028 | 1127 | }, |
| 1029 | 1128 | }); |
| 1030 | 1129 | |
| 1130 | + try { |
| 1131 | + const menuButton = await waitForElement( |
| 1132 | + () => document.querySelector('button[aria-label="Copy options"]'), |
| 1133 | + "Unable to find Open picker button.", |
| 1134 | + ); |
| 1135 | + (menuButton as HTMLButtonElement).click(); |
| 1136 | + |
| 1137 | + await waitForElement( |
| 1138 | + () => |
| 1139 | + Array.from(document.querySelectorAll('[data-slot="menu-item"]')).find((item) => |
| 1140 | + item.textContent?.includes("VS Code Insiders"), |
| 1141 | + ) ?? null, |
| 1142 | + "Unable to find VS Code Insiders menu item.", |
| 1143 | + ); |
| 1144 | + |
| 1145 | + expect( |
| 1146 | + Array.from(document.querySelectorAll('[data-slot="menu-item"]')).some((item) => |
| 1147 | + item.textContent?.includes("Zed"), |
| 1148 | + ), |
| 1149 | + ).toBe(false); |
| 1150 | + |
| 1151 | + const vscodiumItem = await waitForElement( |
| 1152 | + () => |
| 1153 | + Array.from(document.querySelectorAll('[data-slot="menu-item"]')).find((item) => |
| 1154 | + item.textContent?.includes("VSCodium"), |
| 1155 | + ) ?? null, |
| 1156 | + "Unable to find VSCodium menu item.", |
| 1157 | + ); |
| 1158 | + (vscodiumItem as HTMLElement).click(); |
| 1159 | + |
| 1160 | + await vi.waitFor( |
| 1161 | + () => { |
| 1162 | + const openRequest = wsRequests.find( |
| 1163 | + (request) => request._tag === WS_METHODS.shellOpenInEditor, |
| 1164 | + ); |
| 1165 | + expect(openRequest).toMatchObject({ |
| 1166 | + _tag: WS_METHODS.shellOpenInEditor, |
| 1167 | + cwd: "/repo/project", |
| 1168 | + editor: "vscodium", |
| 1169 | + }); |
| 1170 | + }, |
| 1171 | + { timeout: 8_000, interval: 16 }, |
| 1172 | + ); |
| 1173 | + } finally { |
| 1174 | + await mounted.cleanup(); |
| 1175 | + } |
| 1176 | + }); |
| 1177 | + |
| 1178 | + it("falls back to the first installed editor when the stored favorite is unavailable", async () => { |
| 1179 | + localStorage.setItem("t3code:last-editor", "vscodium"); |
| 1180 | + setDraftThreadWithoutWorktree(); |
| 1181 | + |
| 1031 | 1182 | const mounted = await mountChatView({ |
| 1032 | 1183 | viewport: DEFAULT_VIEWPORT, |
| 1033 | 1184 | snapshot: createDraftOnlySnapshot(), |
| 1034 | 1185 | configureFixture: (nextFixture) => { |
| 1035 | 1186 | nextFixture.serverConfig = { |
| 1036 | 1187 | ...nextFixture.serverConfig, |
| 1037 | | − availableEditors: ["vscode"], |
| 1188 | + availableEditors: ["vscode-insiders"], |
| 1038 | 1189 | }; |
| 1039 | 1190 | }, |
| 1040 | 1191 | }); |
| @@ -1057,7 +1208,7 @@ describe("ChatView timeline estimator parity (full app)", () => { · 16 unmodified lines |
| 1057 | 1208 | expect(openRequest).toMatchObject({ |
| 1058 | 1209 | _tag: WS_METHODS.shellOpenInEditor, |
| 1059 | 1210 | cwd: "/repo/project", |
| 1060 | | − editor: "vscode", |
| 1211 | + editor: "vscode-insiders", |
| 1061 | 1212 | }); |
| 1062 | 1213 | }, |
| 1063 | 1214 | { timeout: 8_000, interval: 16 }, |