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

Commit52df12c

Browse files
authored
refactor(storage): split storage.ts into isolated modules; add unit tests; upgrade vitest (#589)
Closes#588This PR refactors `storage.ts` into small, focused modules that are straightforward to unit test (with mocks). It also upgrades `vitest` to a version that plays nicely with VS Code extensions so we can view coverage and run/debug tests directly in VS Code.Key changes- Extract path resolution from `storage.ts` → dedicated module- Extract memento & secrets management from `storage.ts` → dedicated module- Extract and fully separate CLI management logic → dedicated module- Remove `storage.ts` entirely in favor of the new modules- Add unit tests for the split modules- Upgrade `vitest` and related tooling for VS Code extension testingWhy mock `vscode`?- Unit tests (mocked `vscode`): fast, reliable, deterministic validation of module behavior without depending on VS Code APIs or external calls (e.g., Axios).- Integration/E2E tests (real VS Code): cover end-to-end flows by launching VS Code (and eventually a server). Valuable but slower and harder to automate; we reserve these for scenarios that require the actual runtime.
1 parentdc4d6d4 commit52df12c

24 files changed

+2760
-1224
lines changed

‎package.json‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,6 @@
316316
"eventsource":"^3.0.6",
317317
"find-process":"https://github.com/coder/find-process#fix/sequoia-compat",
318318
"jsonc-parser":"^3.3.1",
319-
"memfs":"^4.17.1",
320319
"node-forge":"^1.3.1",
321320
"openpgp":"^6.2.0",
322321
"pretty-bytes":"^7.0.0",
@@ -336,6 +335,7 @@
336335
"@types/ws":"^8.18.1",
337336
"@typescript-eslint/eslint-plugin":"^7.0.0",
338337
"@typescript-eslint/parser":"^6.21.0",
338+
"@vitest/coverage-v8":"^3.2.4",
339339
"@vscode/test-cli":"^0.0.11",
340340
"@vscode/test-electron":"^2.5.2",
341341
"@vscode/vsce":"^3.6.0",
@@ -350,12 +350,13 @@
350350
"eslint-plugin-prettier":"^5.4.1",
351351
"glob":"^10.4.2",
352352
"jsonc-eslint-parser":"^2.4.0",
353+
"memfs":"^4.46.0",
353354
"nyc":"^17.1.0",
354355
"prettier":"^3.5.3",
355356
"ts-loader":"^9.5.1",
356357
"typescript":"^5.8.3",
357358
"utf-8-validate":"^6.0.5",
358-
"vitest":"^0.34.6",
359+
"vitest":"^3.2.4",
359360
"vscode-test":"^1.5.0",
360361
"webpack":"^5.99.6",
361362
"webpack-cli":"^5.1.4"

‎src/__mocks__/testHelpers.ts‎

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
import{vi}from"vitest";
2+
import*asvscodefrom"vscode";
3+
4+
/**
5+
* Mock configuration provider that integrates with the vscode workspace configuration mock.
6+
* Use this to set configuration values that will be returned by vscode.workspace.getConfiguration().
7+
*/
8+
exportclassMockConfigurationProvider{
9+
privateconfig=newMap<string,unknown>();
10+
11+
constructor(){
12+
this.setupVSCodeMock();
13+
}
14+
15+
/**
16+
* Set a configuration value that will be returned by vscode.workspace.getConfiguration().get()
17+
*/
18+
set(key:string,value:unknown):void{
19+
this.config.set(key,value);
20+
}
21+
22+
/**
23+
* Get a configuration value (for testing purposes)
24+
*/
25+
get<T>(key:string):T|undefined;
26+
get<T>(key:string,defaultValue:T):T;
27+
get<T>(key:string,defaultValue?:T):T|undefined{
28+
constvalue=this.config.get(key);
29+
returnvalue!==undefined ?(valueasT) :defaultValue;
30+
}
31+
32+
/**
33+
* Clear all configuration values
34+
*/
35+
clear():void{
36+
this.config.clear();
37+
}
38+
39+
/**
40+
* Setup the vscode.workspace.getConfiguration mock to return our values
41+
*/
42+
privatesetupVSCodeMock():void{
43+
vi.mocked(vscode.workspace.getConfiguration).mockImplementation(
44+
(section?:string)=>{
45+
// Create a snapshot of the current config when getConfiguration is called
46+
constsnapshot=newMap(this.config);
47+
constgetFullKey=(part:string)=>
48+
section ?`${section}.${part}` :part;
49+
50+
return{
51+
get:vi.fn((key:string,defaultValue?:unknown)=>{
52+
constvalue=snapshot.get(getFullKey(key));
53+
returnvalue!==undefined ?value :defaultValue;
54+
}),
55+
has:vi.fn((key:string)=>{
56+
returnsnapshot.has(getFullKey(key));
57+
}),
58+
inspect:vi.fn(),
59+
update:vi.fn((key:string,value:unknown)=>{
60+
this.config.set(getFullKey(key),value);
61+
returnPromise.resolve();
62+
}),
63+
};
64+
},
65+
);
66+
}
67+
}
68+
69+
/**
70+
* Mock progress reporter that integrates with vscode.window.withProgress.
71+
* Use this to control progress reporting behavior and cancellation in tests.
72+
*/
73+
exportclassMockProgressReporter{
74+
privateshouldCancel=false;
75+
privateprogressReports:Array<{message?:string;increment?:number}>=[];
76+
77+
constructor(){
78+
this.setupVSCodeMock();
79+
}
80+
81+
/**
82+
* Set whether the progress should be cancelled
83+
*/
84+
setCancellation(cancel:boolean):void{
85+
this.shouldCancel=cancel;
86+
}
87+
88+
/**
89+
* Get all progress reports that were made
90+
*/
91+
getProgressReports():Array<{message?:string;increment?:number}>{
92+
return[...this.progressReports];
93+
}
94+
95+
/**
96+
* Clear all progress reports
97+
*/
98+
clearProgressReports():void{
99+
this.progressReports=[];
100+
}
101+
102+
/**
103+
* Setup the vscode.window.withProgress mock
104+
*/
105+
privatesetupVSCodeMock():void{
106+
vi.mocked(vscode.window.withProgress).mockImplementation(
107+
async<T>(
108+
_options:vscode.ProgressOptions,
109+
task:(
110+
progress:vscode.Progress<{message?:string;increment?:number}>,
111+
token:vscode.CancellationToken,
112+
)=>Thenable<T>,
113+
):Promise<T>=>{
114+
constprogress={
115+
report:vi.fn((value:{message?:string;increment?:number})=>{
116+
this.progressReports.push(value);
117+
}),
118+
};
119+
120+
constcancellationToken:vscode.CancellationToken={
121+
isCancellationRequested:this.shouldCancel,
122+
onCancellationRequested:vi.fn((listener:(x:unknown)=>void)=>{
123+
if(this.shouldCancel){
124+
setTimeout(listener,0);
125+
}
126+
return{dispose:vi.fn()};
127+
}),
128+
};
129+
130+
returntask(progress,cancellationToken);
131+
},
132+
);
133+
}
134+
}
135+
136+
/**
137+
* Mock user interaction that integrates with vscode.window message dialogs.
138+
* Use this to control user responses in tests.
139+
*/
140+
exportclassMockUserInteraction{
141+
privateresponses=newMap<string,string|undefined>();
142+
privateexternalUrls:string[]=[];
143+
144+
constructor(){
145+
this.setupVSCodeMock();
146+
}
147+
148+
/**
149+
* Set a response for a specific message
150+
*/
151+
setResponse(message:string,response:string|undefined):void{
152+
this.responses.set(message,response);
153+
}
154+
155+
/**
156+
* Get all URLs that were opened externally
157+
*/
158+
getExternalUrls():string[]{
159+
return[...this.externalUrls];
160+
}
161+
162+
/**
163+
* Clear all external URLs
164+
*/
165+
clearExternalUrls():void{
166+
this.externalUrls=[];
167+
}
168+
169+
/**
170+
* Clear all responses
171+
*/
172+
clearResponses():void{
173+
this.responses.clear();
174+
}
175+
176+
/**
177+
* Setup the vscode.window message dialog mocks
178+
*/
179+
privatesetupVSCodeMock():void{
180+
constgetResponse=(message:string):string|undefined=>{
181+
returnthis.responses.get(message);
182+
};
183+
184+
vi.mocked(vscode.window.showErrorMessage).mockImplementation(
185+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
186+
(message:string):Thenable<any>=>{
187+
constresponse=getResponse(message);
188+
returnPromise.resolve(response);
189+
},
190+
);
191+
192+
vi.mocked(vscode.window.showWarningMessage).mockImplementation(
193+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
194+
(message:string):Thenable<any>=>{
195+
constresponse=getResponse(message);
196+
returnPromise.resolve(response);
197+
},
198+
);
199+
200+
vi.mocked(vscode.window.showInformationMessage).mockImplementation(
201+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
202+
(message:string):Thenable<any>=>{
203+
constresponse=getResponse(message);
204+
returnPromise.resolve(response);
205+
},
206+
);
207+
208+
vi.mocked(vscode.env.openExternal).mockImplementation(
209+
(target:vscode.Uri):Promise<boolean>=>{
210+
this.externalUrls.push(target.toString());
211+
returnPromise.resolve(true);
212+
},
213+
);
214+
}
215+
}
216+
217+
// Simple in-memory implementation of Memento
218+
exportclassInMemoryMementoimplementsvscode.Memento{
219+
privatestorage=newMap<string,unknown>();
220+
221+
get<T>(key:string):T|undefined;
222+
get<T>(key:string,defaultValue:T):T;
223+
get<T>(key:string,defaultValue?:T):T|undefined{
224+
returnthis.storage.has(key) ?(this.storage.get(key)asT) :defaultValue;
225+
}
226+
227+
asyncupdate(key:string,value:unknown):Promise<void>{
228+
if(value===undefined){
229+
this.storage.delete(key);
230+
}else{
231+
this.storage.set(key,value);
232+
}
233+
returnPromise.resolve();
234+
}
235+
236+
keys():readonlystring[]{
237+
returnArray.from(this.storage.keys());
238+
}
239+
}
240+
241+
// Simple in-memory implementation of SecretStorage
242+
exportclassInMemorySecretStorageimplementsvscode.SecretStorage{
243+
privatesecrets=newMap<string,string>();
244+
privateisCorrupted=false;
245+
246+
onDidChange:vscode.Event<vscode.SecretStorageChangeEvent>=()=>({
247+
dispose:()=>{},
248+
});
249+
250+
asyncget(key:string):Promise<string|undefined>{
251+
if(this.isCorrupted){
252+
returnPromise.reject(newError("Storage corrupted"));
253+
}
254+
returnthis.secrets.get(key);
255+
}
256+
257+
asyncstore(key:string,value:string):Promise<void>{
258+
if(this.isCorrupted){
259+
returnPromise.reject(newError("Storage corrupted"));
260+
}
261+
this.secrets.set(key,value);
262+
}
263+
264+
asyncdelete(key:string):Promise<void>{
265+
if(this.isCorrupted){
266+
returnPromise.reject(newError("Storage corrupted"));
267+
}
268+
this.secrets.delete(key);
269+
}
270+
271+
corruptStorage():void{
272+
this.isCorrupted=true;
273+
}
274+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp