test(core): Add test coverage for progressCallback in runDefaultAction

Add three test cases for handleProgress:
- Callback is invoked with progress messages from pack()
- Async callback rejection is isolated (pack completes, error logged)
- Spinner still updates when callback throws synchronously

Also restore the outer try-catch for sync errors — Promise.resolve()
only catches async rejections, not synchronous throws from the
callback invocation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kazuki Yamada
2026-04-06 19:33:23 +09:00
parent 01bb331238
commit cb00a7cbc0
2 changed files with 96 additions and 2 deletions
+6 -2
View File
@@ -118,9 +118,13 @@ export const runDefaultAction = async (
const handleProgress: RepomixProgressCallback = (message) => {
spinner.update(message);
if (progressCallback) {
Promise.resolve(progressCallback(message)).catch((error) => {
try {
Promise.resolve(progressCallback(message)).catch((error) => {
logger.trace('progressCallback error:', error);
});
} catch (error) {
logger.trace('progressCallback error:', error);
});
}
}
};
+90
View File
@@ -6,6 +6,7 @@ import * as configLoader from '../../../src/config/configLoad.js';
import * as fileStdin from '../../../src/core/file/fileStdin.js';
import * as packageJsonParser from '../../../src/core/file/packageJsonParse.js';
import * as packager from '../../../src/core/packager.js';
import * as loggerModule from '../../../src/shared/logger.js';
import { createMockConfig } from '../../testing/testUtils.js';
vi.mock('../../../src/core/packager');
@@ -156,6 +157,95 @@ describe('defaultAction', () => {
expect(mockSpinner.fail).toHaveBeenCalledWith('Error during packing');
});
describe('progressCallback', () => {
it('should forward progress messages to the provided callback', async () => {
// Configure pack mock to invoke its 3rd argument (progressCallback)
vi.mocked(packager.pack).mockImplementation(async (_paths, _config, progressCallback = () => {}) => {
progressCallback('Searching for files...');
progressCallback('Processing files...');
return {
totalFiles: 10,
totalCharacters: 1000,
totalTokens: 200,
fileCharCounts: {},
fileTokenCounts: {},
suspiciousFilesResults: [],
suspiciousGitDiffResults: [],
suspiciousGitLogResults: [],
processedFiles: [],
safeFilePaths: [],
gitDiffTokenCount: 0,
gitLogTokenCount: 0,
skippedFiles: [],
};
});
const callback = vi.fn();
await runDefaultAction(['.'], process.cwd(), {}, callback);
expect(callback).toHaveBeenCalledWith('Searching for files...');
expect(callback).toHaveBeenCalledWith('Processing files...');
});
it('should isolate async callback errors without affecting pack flow', async () => {
vi.mocked(packager.pack).mockImplementation(async (_paths, _config, progressCallback = () => {}) => {
progressCallback('test message');
// Allow microtask to process the rejected promise
await new Promise((resolve) => setTimeout(resolve, 10));
return {
totalFiles: 10,
totalCharacters: 1000,
totalTokens: 200,
fileCharCounts: {},
fileTokenCounts: {},
suspiciousFilesResults: [],
suspiciousGitDiffResults: [],
suspiciousGitLogResults: [],
processedFiles: [],
safeFilePaths: [],
gitDiffTokenCount: 0,
gitLogTokenCount: 0,
skippedFiles: [],
};
});
const rejectingCallback = vi.fn().mockRejectedValue(new Error('callback error'));
const result = await runDefaultAction(['.'], process.cwd(), {}, rejectingCallback);
expect(result.packResult.totalFiles).toBe(10);
expect(loggerModule.logger.trace).toHaveBeenCalledWith('progressCallback error:', expect.any(Error));
});
it('should still update spinner even when callback throws synchronously', async () => {
vi.mocked(packager.pack).mockImplementation(async (_paths, _config, progressCallback = () => {}) => {
progressCallback('test message');
return {
totalFiles: 10,
totalCharacters: 1000,
totalTokens: 200,
fileCharCounts: {},
fileTokenCounts: {},
suspiciousFilesResults: [],
suspiciousGitDiffResults: [],
suspiciousGitLogResults: [],
processedFiles: [],
safeFilePaths: [],
gitDiffTokenCount: 0,
gitLogTokenCount: 0,
skippedFiles: [],
};
});
const throwingCallback = vi.fn().mockImplementation(() => {
throw new Error('sync error');
});
await runDefaultAction(['.'], process.cwd(), {}, throwingCallback);
// Spinner should still be updated despite callback failure
expect(mockSpinner.update).toHaveBeenCalledWith('test message');
});
});
describe('buildCliConfig', () => {
it('should handle custom include patterns', () => {
const options = {