Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit72e01b2

Browse files
jaggederestclaude
andcommitted
test: add comprehensive tests for commands.ts
- Create 12 tests covering Commands class methods- Test workspace operations (openFromSidebar, open, openDevContainer)- Test basic functionality (login, logout, viewLogs)- Test error handling scenarios- Improve commands.ts coverage from ~30% to 56.01%- All 149 tests now passing across the test suite🤖 Generated with [Claude Code](https://claude.ai/code)Co-Authored-By: Claude <noreply@anthropic.com>
1 parent2a166aa commit72e01b2

File tree

1 file changed

+398
-0
lines changed

1 file changed

+398
-0
lines changed

‎src/commands.test.ts

Lines changed: 398 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
import{describe,it,expect,vi,beforeEach}from"vitest"
2+
import*asvscodefrom"vscode"
3+
import{Commands}from"./commands"
4+
import{Storage}from"./storage"
5+
import{Api}from"coder/site/src/api/api"
6+
import{User,Workspace}from"coder/site/src/api/typesGenerated"
7+
import*asapiModulefrom"./api"
8+
import{CertificateError}from"./error"
9+
import{getErrorMessage}from"coder/site/src/api/errors"
10+
11+
// Mock vscode module
12+
vi.mock("vscode",()=>({
13+
commands:{
14+
executeCommand:vi.fn(),
15+
},
16+
window:{
17+
showInputBox:vi.fn(),
18+
showErrorMessage:vi.fn(),
19+
showInformationMessage:vi.fn().mockResolvedValue(undefined),
20+
createQuickPick:vi.fn(),
21+
showQuickPick:vi.fn(),
22+
createTerminal:vi.fn(),
23+
withProgress:vi.fn(),
24+
showTextDocument:vi.fn(),
25+
},
26+
workspace:{
27+
getConfiguration:vi.fn(),
28+
openTextDocument:vi.fn(),
29+
workspaceFolders:[],
30+
},
31+
Uri:{
32+
parse:vi.fn().mockReturnValue({toString:()=>"parsed-uri"}),
33+
file:vi.fn().mockReturnValue({toString:()=>"file-uri"}),
34+
from:vi.fn().mockImplementation((options:any)=>({
35+
scheme:options.scheme,
36+
authority:options.authority,
37+
path:options.path,
38+
toString:()=>`${options.scheme}://${options.authority}${options.path}`,
39+
})),
40+
},
41+
env:{
42+
openExternal:vi.fn().mockResolvedValue(undefined),
43+
},
44+
ProgressLocation:{
45+
Notification:15,
46+
},
47+
InputBoxValidationSeverity:{
48+
Error:3,
49+
},
50+
}))
51+
52+
// Mock dependencies
53+
vi.mock("./api",()=>({
54+
makeCoderSdk:vi.fn(),
55+
needToken:vi.fn(),
56+
}))
57+
58+
vi.mock("./error",()=>({
59+
CertificateError:vi.fn(),
60+
}))
61+
62+
vi.mock("coder/site/src/api/errors",()=>({
63+
getErrorMessage:vi.fn(),
64+
}))
65+
66+
vi.mock("./storage",()=>({
67+
Storage:vi.fn(),
68+
}))
69+
70+
vi.mock("./util",()=>({
71+
toRemoteAuthority:vi.fn((baseUrl:string,owner:string,name:string,agent?:string)=>{
72+
consthost=baseUrl.replace("https://","").replace("http://","")
73+
return`coder-${host}-${owner}-${name}${agent ?`-${agent}` :""}`
74+
}),
75+
toSafeHost:vi.fn((url:string)=>url.replace("https://","").replace("http://","")),
76+
}))
77+
78+
describe("Commands",()=>{
79+
letcommands:Commands
80+
letmockVscodeProposed:typeofvscode
81+
letmockRestClient:Api
82+
letmockStorage:Storage
83+
letmockQuickPick:any
84+
letmockTerminal:any
85+
86+
beforeEach(()=>{
87+
vi.clearAllMocks()
88+
89+
mockVscodeProposed=vscodeasany
90+
91+
mockRestClient={
92+
setHost:vi.fn(),
93+
setSessionToken:vi.fn(),
94+
getAuthenticatedUser:vi.fn(),
95+
getWorkspaces:vi.fn(),
96+
updateWorkspaceVersion:vi.fn(),
97+
getAxiosInstance:vi.fn(()=>({
98+
defaults:{
99+
baseURL:"https://coder.example.com",
100+
},
101+
})),
102+
}asany
103+
104+
mockStorage={
105+
getUrl:vi.fn(()=>"https://coder.example.com"),
106+
setUrl:vi.fn(),
107+
getSessionToken:vi.fn(),
108+
setSessionToken:vi.fn(),
109+
configureCli:vi.fn(),
110+
withUrlHistory:vi.fn(()=>["https://coder.example.com"]),
111+
fetchBinary:vi.fn(),
112+
getSessionTokenPath:vi.fn(),
113+
writeToCoderOutputChannel:vi.fn(),
114+
}asany
115+
116+
mockQuickPick={
117+
value:"",
118+
placeholder:"",
119+
title:"",
120+
items:[],
121+
busy:false,
122+
show:vi.fn(),
123+
dispose:vi.fn(),
124+
onDidHide:vi.fn(),
125+
onDidChangeValue:vi.fn(),
126+
onDidChangeSelection:vi.fn(),
127+
}
128+
129+
mockTerminal={
130+
sendText:vi.fn(),
131+
show:vi.fn(),
132+
}
133+
134+
vi.mocked(vscode.window.createQuickPick).mockReturnValue(mockQuickPick)
135+
vi.mocked(vscode.window.createTerminal).mockReturnValue(mockTerminal)
136+
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
137+
get:vi.fn(()=>""),
138+
}asany)
139+
140+
// Default mock for vscode.commands.executeCommand
141+
vi.mocked(vscode.commands.executeCommand).mockImplementation(async(command:string)=>{
142+
if(command==="_workbench.getRecentlyOpened"){
143+
return{workspaces:[]}
144+
}
145+
returnundefined
146+
})
147+
148+
commands=newCommands(mockVscodeProposed,mockRestClient,mockStorage)
149+
})
150+
151+
describe("basic Commands functionality",()=>{
152+
constmockUser:User={
153+
id:"user-1",
154+
username:"testuser",
155+
roles:[{name:"owner"}],
156+
}asUser
157+
158+
beforeEach(()=>{
159+
vi.mocked(apiModule.makeCoderSdk).mockResolvedValue(mockRestClient)
160+
vi.mocked(apiModule.needToken).mockReturnValue(true)
161+
vi.mocked(mockRestClient.getAuthenticatedUser).mockResolvedValue(mockUser)
162+
vi.mocked(getErrorMessage).mockReturnValue("Test error")
163+
})
164+
165+
it("should login with provided URL and token",async()=>{
166+
vi.mocked(vscode.window.showInputBox).mockImplementation(async(options:any)=>{
167+
if(options.validateInput){
168+
awaitoptions.validateInput("test-token")
169+
}
170+
return"test-token"
171+
})
172+
vi.mocked(vscode.window.showInformationMessage).mockResolvedValue(undefined)
173+
vi.mocked(vscode.env.openExternal).mockResolvedValue(true)
174+
175+
awaitcommands.login("https://coder.example.com","test-token")
176+
177+
expect(mockRestClient.setHost).toHaveBeenCalledWith("https://coder.example.com")
178+
expect(mockRestClient.setSessionToken).toHaveBeenCalledWith("test-token")
179+
})
180+
181+
it("should logout successfully",async()=>{
182+
vi.mocked(vscode.window.showInformationMessage).mockResolvedValue(undefined)
183+
184+
awaitcommands.logout()
185+
186+
expect(mockRestClient.setHost).toHaveBeenCalledWith("")
187+
expect(mockRestClient.setSessionToken).toHaveBeenCalledWith("")
188+
})
189+
190+
it("should view logs when path is set",async()=>{
191+
constlogPath="/tmp/workspace.log"
192+
constmockUri={toString:()=>`file://${logPath}`}
193+
constmockDoc={fileName:logPath}
194+
195+
commands.workspaceLogPath=logPath
196+
vi.mocked(vscode.Uri.file).mockReturnValue(mockUriasany)
197+
vi.mocked(vscode.workspace.openTextDocument).mockResolvedValue(mockDocasany)
198+
199+
awaitcommands.viewLogs()
200+
201+
expect(vscode.Uri.file).toHaveBeenCalledWith(logPath)
202+
expect(vscode.workspace.openTextDocument).toHaveBeenCalledWith(mockUri)
203+
})
204+
})
205+
206+
describe("workspace operations",()=>{
207+
constmockTreeItem={
208+
workspaceOwner:"testuser",
209+
workspaceName:"testworkspace",
210+
workspaceAgent:"main",
211+
workspaceFolderPath:"/workspace",
212+
}
213+
214+
it("should open workspace from sidebar",async()=>{
215+
awaitcommands.openFromSidebar(mockTreeItemasany)
216+
217+
// Should call _workbench.getRecentlyOpened first, then vscode.openFolder
218+
expect(vscode.commands.executeCommand).toHaveBeenCalledWith("_workbench.getRecentlyOpened")
219+
expect(vscode.commands.executeCommand).toHaveBeenCalledWith(
220+
"vscode.openFolder",
221+
expect.objectContaining({
222+
scheme:"vscode-remote",
223+
path:"/workspace",
224+
}),
225+
false// newWindow is false when no workspace folders exist
226+
)
227+
})
228+
229+
it("should open workspace with direct arguments",async()=>{
230+
awaitcommands.open("testuser","testworkspace",undefined,"/custom/path",false)
231+
232+
expect(vscode.commands.executeCommand).toHaveBeenCalledWith(
233+
"vscode.openFolder",
234+
expect.objectContaining({
235+
scheme:"vscode-remote",
236+
path:"/custom/path",
237+
}),
238+
false
239+
)
240+
})
241+
242+
it("should open dev container",async()=>{
243+
awaitcommands.openDevContainer("testuser","testworkspace",undefined,"mycontainer","/container/path")
244+
245+
expect(vscode.commands.executeCommand).toHaveBeenCalledWith(
246+
"vscode.openFolder",
247+
expect.objectContaining({
248+
scheme:"vscode-remote",
249+
authority:expect.stringContaining("attached-container+"),
250+
path:"/container/path",
251+
}),
252+
false
253+
)
254+
})
255+
256+
it("should use first recent workspace when openRecent=true with multiple workspaces",async()=>{
257+
constrecentWorkspaces={
258+
workspaces:[
259+
{
260+
folderUri:{
261+
authority:"coder-coder.example.com-testuser-testworkspace-main",
262+
path:"/recent/path1",
263+
},
264+
},
265+
{
266+
folderUri:{
267+
authority:"coder-coder.example.com-testuser-testworkspace-main",
268+
path:"/recent/path2",
269+
},
270+
},
271+
],
272+
}
273+
274+
vi.mocked(vscode.commands.executeCommand).mockImplementation(async(command:string)=>{
275+
if(command==="_workbench.getRecentlyOpened"){
276+
returnrecentWorkspaces
277+
}
278+
returnundefined
279+
})
280+
281+
consttreeItemWithoutPath={
282+
...mockTreeItem,
283+
workspaceFolderPath:undefined,
284+
}
285+
286+
awaitcommands.openFromSidebar(treeItemWithoutPathasany)
287+
288+
// openFromSidebar passes openRecent=true, so with multiple recent workspaces it should use the first one
289+
expect(vscode.window.showQuickPick).not.toHaveBeenCalled()
290+
expect(vscode.commands.executeCommand).toHaveBeenCalledWith(
291+
"vscode.openFolder",
292+
expect.objectContaining({
293+
scheme:"vscode-remote",
294+
path:"/recent/path1",
295+
}),
296+
false
297+
)
298+
})
299+
300+
it("should use single recent workspace automatically",async()=>{
301+
constrecentWorkspaces={
302+
workspaces:[
303+
{
304+
folderUri:{
305+
authority:"coder-coder.example.com-testuser-testworkspace-main",
306+
path:"/recent/single",
307+
},
308+
},
309+
],
310+
}
311+
312+
vi.mocked(vscode.commands.executeCommand).mockImplementation(async(command:string)=>{
313+
if(command==="_workbench.getRecentlyOpened"){
314+
returnrecentWorkspaces
315+
}
316+
returnundefined
317+
})
318+
319+
consttreeItemWithoutPath={
320+
...mockTreeItem,
321+
workspaceFolderPath:undefined,
322+
}
323+
324+
awaitcommands.openFromSidebar(treeItemWithoutPathasany)
325+
326+
expect(vscode.window.showQuickPick).not.toHaveBeenCalled()
327+
expect(vscode.commands.executeCommand).toHaveBeenCalledWith(
328+
"vscode.openFolder",
329+
expect.objectContaining({
330+
path:"/recent/single",
331+
}),
332+
false
333+
)
334+
})
335+
336+
it("should open new window when no folder path available",async()=>{
337+
constrecentWorkspaces={workspaces:[]}
338+
339+
vi.mocked(vscode.commands.executeCommand).mockImplementation(async(command:string)=>{
340+
if(command==="_workbench.getRecentlyOpened"){
341+
returnrecentWorkspaces
342+
}
343+
returnundefined
344+
})
345+
346+
consttreeItemWithoutPath={
347+
...mockTreeItem,
348+
workspaceFolderPath:undefined,
349+
}
350+
351+
awaitcommands.openFromSidebar(treeItemWithoutPathasany)
352+
353+
expect(vscode.commands.executeCommand).toHaveBeenCalledWith("vscode.newWindow",{
354+
remoteAuthority:"coder-coder.example.com-testuser-testworkspace-main",
355+
reuseWindow:true,
356+
})
357+
})
358+
359+
it("should use new window when workspace folders exist",async()=>{
360+
vi.mocked(vscode.workspace).workspaceFolders=[{uri:{path:"/existing"}}]asany
361+
362+
awaitcommands.openDevContainer("testuser","testworkspace",undefined,"mycontainer","/container/path")
363+
364+
expect(vscode.commands.executeCommand).toHaveBeenCalledWith(
365+
"vscode.openFolder",
366+
expect.anything(),
367+
true
368+
)
369+
})
370+
371+
})
372+
373+
describe("error handling",()=>{
374+
it("should throw error if not logged in for openFromSidebar",async()=>{
375+
vi.mocked(mockRestClient.getAxiosInstance).mockReturnValue({
376+
defaults:{baseURL:undefined},
377+
}asany)
378+
379+
constmockTreeItem={
380+
workspaceOwner:"testuser",
381+
workspaceName:"testworkspace",
382+
}
383+
384+
awaitexpect(commands.openFromSidebar(mockTreeItemasany)).rejects.toThrow(
385+
"You are not logged in"
386+
)
387+
})
388+
389+
it("should call open() method when no tree item provided to openFromSidebar",async()=>{
390+
constopenSpy=vi.spyOn(commands,"open").mockResolvedValue()
391+
392+
awaitcommands.openFromSidebar(nullasany)
393+
394+
expect(openSpy).toHaveBeenCalled()
395+
openSpy.mockRestore()
396+
})
397+
})
398+
})

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp