Skip to content

Commit 7b1ee18

Browse files
fix: implement just-in-time test file list updates
This change optimizes file system event handling by implementing a just-in-time approach to updating test file lists. Rather than immediately refreshing the test file list on every file system event, we now mark it as dirty and only update it when necessary (before running tests). - Add testFileListDirty flag to track when updates are needed - Only update test file list when running or debugging tests - Force update on initial startup for completeness - Significantly improves performance when renaming files in large repositories fixes jest-community#1196
1 parent 92b7162 commit 7b1ee18

File tree

4 files changed

+101
-17
lines changed

4 files changed

+101
-17
lines changed

src/JestExt/core.ts

+22-5
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,8 @@ export class JestExt {
323323

324324
this.events.onTestSessionStarted.fire({ ...this.extContext, session: this.processSession });
325325

326-
await this.updateTestFileList();
326+
// Force update test file list during session initialization
327+
await this.updateTestFileList(true);
327328

328329
// update visible editors that belong to this folder
329330
this.updateVisibleTextEditors();
@@ -568,6 +569,11 @@ export class JestExt {
568569

569570
//** commands */
570571
public debugTests = async (debugInfo: DebugInfo): Promise<void> => {
572+
// Check if test file list needs updating before debug, and update if necessary
573+
if (this.testResultProvider.isTestFileListDirty()) {
574+
await this.updateTestFileList();
575+
}
576+
571577
const getDebugConfig = (
572578
folder?: vscode.WorkspaceFolder
573579
): vscode.DebugConfiguration | undefined => {
@@ -619,6 +625,11 @@ export class JestExt {
619625
this.enableOutputOnRun();
620626
await this.exitDeferMode();
621627

628+
// Check if test file list needs updating, and update if necessary
629+
if (this.testResultProvider.isTestFileListDirty()) {
630+
await this.updateTestFileList();
631+
}
632+
622633
if (!editor) {
623634
if (this.processSession.scheduleProcess({ type: 'all-tests', nonBlocking: true })) {
624635
this.dirtyFiles.clear();
@@ -703,6 +714,7 @@ export class JestExt {
703714
private refreshDocumentChange(document?: vscode.TextDocument): void {
704715
this.updateVisibleTextEditors(document);
705716

717+
// Update status bar with latest stats
706718
this.updateStatusBar({
707719
stats: this.toSBStats(this.testResultProvider.getTestSuiteStats()),
708720
});
@@ -741,7 +753,12 @@ export class JestExt {
741753
this.refreshDocumentChange(document);
742754
}
743755

744-
private async updateTestFileList(): Promise<void> {
756+
private async updateTestFileList(force: boolean = false): Promise<void> {
757+
// Skip update if not forced and test file list isn't dirty
758+
if (!force && !this.testResultProvider.isTestFileListDirty()) {
759+
return;
760+
}
761+
745762
return new Promise((resolve, reject) => {
746763
this.processSession.scheduleProcess({
747764
type: 'list-test-files',
@@ -765,13 +782,13 @@ export class JestExt {
765782
}
766783

767784
onDidCreateFiles(_event: vscode.FileCreateEvent): void {
768-
this.updateTestFileList();
785+
this.testResultProvider.markTestFileListDirty();
769786
}
770787
onDidRenameFiles(_event: vscode.FileRenameEvent): void {
771-
this.updateTestFileList();
788+
this.testResultProvider.markTestFileListDirty();
772789
}
773790
onDidDeleteFiles(_event: vscode.FileDeleteEvent): void {
774-
this.updateTestFileList();
791+
this.testResultProvider.markTestFileListDirty();
775792
}
776793

777794
toggleCoverage(): Promise<void> {

src/TestResults/TestResultProvider.ts

+10
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ export class TestResultProvider {
194194
private testFiles?: string[];
195195
private snapshotProvider: SnapshotProvider;
196196
private parser: Parser;
197+
private testFileListDirty: boolean = false;
197198

198199
constructor(
199200
extEvents: JestSessionEvents,
@@ -258,12 +259,21 @@ export class TestResultProvider {
258259

259260
updateTestFileList(testFiles?: string[]): void {
260261
this.testFiles = testFiles;
262+
this.testFileListDirty = false;
261263

262264
// clear the cache in case we have cached some non-test files prior
263265
this.testSuites.clear();
264266

265267
this.events.testListUpdated.fire(testFiles);
266268
}
269+
270+
markTestFileListDirty(): void {
271+
this.testFileListDirty = true;
272+
}
273+
274+
isTestFileListDirty(): boolean {
275+
return this.testFileListDirty;
276+
}
267277
getTestList(): string[] {
268278
if (this.testFiles && this.testFiles.length > 0) {
269279
return this.testFiles;

tests/JestExt/core.test.ts

+60-12
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,20 @@ describe('JestExt', () => {
112112
const coverageCodeLensProvider: any = override?.coverageCodeLensProvider ?? {
113113
coverageChanged: jest.fn(),
114114
};
115-
return new JestExt(
115+
116+
// Make a JestExt instance
117+
const jestExtInstance = new JestExt(
116118
context,
117119
workspaceFolder,
118120
debugConfigurationProvider,
119121
coverageCodeLensProvider
120122
);
123+
124+
// Mock the new methods on testResultProvider
125+
jestExtInstance.testResultProvider.markTestFileListDirty = jest.fn();
126+
jestExtInstance.testResultProvider.isTestFileListDirty = jest.fn().mockReturnValue(false);
127+
128+
return jestExtInstance;
121129
};
122130
const mockEditor = (fileName: string, languageId = 'typescript'): any => {
123131
return {
@@ -178,6 +186,22 @@ describe('JestExt', () => {
178186
let startDebugging;
179187
const mockShowQuickPick = jest.fn();
180188
let mockConfigurations = [];
189+
190+
it('should update test file list if marked as dirty before debugging', async () => {
191+
const sut = newJestExt();
192+
// Mock that test file list is dirty
193+
(sut.testResultProvider.isTestFileListDirty as jest.Mock).mockReturnValueOnce(true);
194+
195+
// Set up updateTestFileList to resolve immediately when called
196+
const updateTestFileListSpy = jest.spyOn(sut as any, 'updateTestFileList').mockResolvedValueOnce(undefined);
197+
198+
await sut.debugTests({ testPath: document.fileName, testName: 'testName' });
199+
200+
// Verify updateTestFileList was called before debugging
201+
expect(updateTestFileListSpy).toHaveBeenCalled();
202+
expect(sut.debugConfigurationProvider.prepareTestRun).toHaveBeenCalled();
203+
});
204+
181205
beforeEach(() => {
182206
startDebugging = vscode.debug.startDebugging as unknown as jest.Mock<{}>;
183207
(startDebugging as unknown as jest.Mock<{}>).mockImplementation(
@@ -752,6 +776,19 @@ describe('JestExt', () => {
752776
expect(mockTestProvider.dispose).toHaveBeenCalledTimes(1);
753777
expect(JestTestProvider).toHaveBeenCalledTimes(2);
754778
});
779+
780+
it('forces update of test file list on session start', async () => {
781+
const sut = createJestExt();
782+
783+
// Set up spy to check how updateTestFileList is called
784+
const updateTestFileListSpy = jest.spyOn(sut as any, 'updateTestFileList');
785+
786+
await sut.startSession();
787+
788+
// Verify updateTestFileList was called with force=true
789+
expect(updateTestFileListSpy).toHaveBeenCalledWith(true);
790+
});
791+
755792
describe('will update test file list', () => {
756793
it.each`
757794
fileNames | error | expectedTestFiles
@@ -861,6 +898,21 @@ describe('JestExt', () => {
861898
});
862899
});
863900
describe('runAllTests', () => {
901+
it('should update test file list if marked as dirty before running tests', async () => {
902+
const sut = newJestExt();
903+
// Mock that test file list is dirty
904+
(sut.testResultProvider.isTestFileListDirty as jest.Mock).mockReturnValueOnce(true);
905+
906+
// Set up updateTestFileList to resolve immediately when called
907+
const updateTestFileListSpy = jest.spyOn(sut as any, 'updateTestFileList').mockResolvedValueOnce(undefined);
908+
909+
await sut.runAllTests();
910+
911+
// Verify updateTestFileList was called before scheduling process
912+
expect(updateTestFileListSpy).toHaveBeenCalled();
913+
expect(mockProcessSession.scheduleProcess).toHaveBeenCalled();
914+
});
915+
864916
describe.each`
865917
scheduleProcess
866918
${{}}
@@ -934,29 +986,25 @@ describe('JestExt', () => {
934986
}
935987
);
936988
});
937-
describe('refresh test file list upon file system change', () => {
938-
const getProcessType = () => {
939-
const { type } = mockProcessSession.scheduleProcess.mock.calls[0][0];
940-
return type;
941-
};
989+
describe('mark test file list as dirty upon file system change', () => {
942990
let jestExt: any;
943991
beforeEach(() => {
944992
jestExt = newJestExt();
945993
});
946994
it('when new file is created', () => {
947995
jestExt.onDidCreateFiles({});
948-
expect(mockProcessSession.scheduleProcess).toHaveBeenCalledTimes(1);
949-
expect(getProcessType()).toEqual('list-test-files');
996+
expect(jestExt.testResultProvider.markTestFileListDirty).toHaveBeenCalled();
997+
expect(mockProcessSession.scheduleProcess).not.toHaveBeenCalled();
950998
});
951999
it('when file is renamed', () => {
9521000
jestExt.onDidRenameFiles({});
953-
expect(mockProcessSession.scheduleProcess).toHaveBeenCalledTimes(1);
954-
expect(getProcessType()).toEqual('list-test-files');
1001+
expect(jestExt.testResultProvider.markTestFileListDirty).toHaveBeenCalled();
1002+
expect(mockProcessSession.scheduleProcess).not.toHaveBeenCalled();
9551003
});
9561004
it('when file is deleted', () => {
9571005
jestExt.onDidDeleteFiles({});
958-
expect(mockProcessSession.scheduleProcess).toHaveBeenCalledTimes(1);
959-
expect(getProcessType()).toEqual('list-test-files');
1006+
expect(jestExt.testResultProvider.markTestFileListDirty).toHaveBeenCalled();
1007+
expect(mockProcessSession.scheduleProcess).not.toHaveBeenCalled();
9601008
});
9611009
});
9621010
describe('triggerUpdateSettings', () => {

tests/TestResults/TestResultProvider.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,15 @@ describe('TestResultProvider', () => {
570570
sut.updateTestFileList(['test-file']);
571571
itBlocks = [];
572572
};
573+
574+
it('should mark test file list as not dirty after update', () => {
575+
const sut = new TestResultProvider(eventsMock);
576+
expect(sut.isTestFileListDirty()).toBeFalsy();
577+
sut.markTestFileListDirty();
578+
expect(sut.isTestFileListDirty()).toBeTruthy();
579+
sut.updateTestFileList(['test-file']);
580+
expect(sut.isTestFileListDirty()).toBeFalsy();
581+
});
573582
it.each`
574583
desc | setup | itBlockOverride | expectedResults | statsChange
575584
${'parse failed'} | ${forceParseError} | ${undefined} | ${[]} | ${'fail'}

0 commit comments

Comments
 (0)