@amirdaraee/namewise 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/index.js +0 -0
- package/package.json +2 -2
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -82
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -61
- package/.github/workflows/auto-release.yml +0 -78
- package/.github/workflows/build.yml +0 -55
- package/.github/workflows/publish.yml +0 -134
- package/.github/workflows/test.yml +0 -47
- package/eng.traineddata +0 -0
- package/src/cli/commands.ts +0 -64
- package/src/cli/rename.ts +0 -171
- package/src/index.ts +0 -54
- package/src/parsers/excel-parser.ts +0 -66
- package/src/parsers/factory.ts +0 -38
- package/src/parsers/pdf-parser.ts +0 -99
- package/src/parsers/text-parser.ts +0 -43
- package/src/parsers/word-parser.ts +0 -50
- package/src/services/ai-factory.ts +0 -39
- package/src/services/claude-service.ts +0 -119
- package/src/services/file-renamer.ts +0 -141
- package/src/services/lmstudio-service.ts +0 -161
- package/src/services/ollama-service.ts +0 -191
- package/src/services/openai-service.ts +0 -117
- package/src/types/index.ts +0 -76
- package/src/types/pdf-extraction.d.ts +0 -7
- package/src/utils/ai-prompts.ts +0 -76
- package/src/utils/file-templates.ts +0 -275
- package/src/utils/naming-conventions.ts +0 -67
- package/src/utils/pdf-to-image.ts +0 -137
- package/tests/data/console-test-1.txt +0 -1
- package/tests/data/console-test-2.txt +0 -1
- package/tests/data/console-test-long-filename-for-display-testing.txt +0 -1
- package/tests/data/empty-file.txt +0 -0
- package/tests/data/failure.txt +0 -1
- package/tests/data/file1.txt +0 -1
- package/tests/data/file2.txt +0 -1
- package/tests/data/much-longer-filename-to-test-clearing.txt +0 -1
- package/tests/data/sample-markdown.md +0 -9
- package/tests/data/sample-pdf.pdf +0 -0
- package/tests/data/sample-text.txt +0 -25
- package/tests/data/short.txt +0 -1
- package/tests/data/single-file.txt +0 -1
- package/tests/data/success.txt +0 -1
- package/tests/data/this-is-a-very-long-filename-that-should-be-truncated-for-better-display-purposes.txt +0 -1
- package/tests/data/very-long-filename-that-should-be-cleared-properly.txt +0 -1
- package/tests/data/x.txt +0 -1
- package/tests/integration/ai-prompting.test.ts +0 -386
- package/tests/integration/end-to-end.test.ts +0 -209
- package/tests/integration/person-name-extraction.test.ts +0 -440
- package/tests/integration/workflow.test.ts +0 -336
- package/tests/mocks/mock-ai-service.ts +0 -58
- package/tests/unit/cli/commands.test.ts +0 -169
- package/tests/unit/parsers/factory.test.ts +0 -100
- package/tests/unit/parsers/pdf-parser.test.ts +0 -63
- package/tests/unit/parsers/text-parser.test.ts +0 -85
- package/tests/unit/services/ai-factory.test.ts +0 -85
- package/tests/unit/services/claude-service.test.ts +0 -188
- package/tests/unit/services/file-renamer.test.ts +0 -514
- package/tests/unit/services/lmstudio-service.test.ts +0 -326
- package/tests/unit/services/ollama-service.test.ts +0 -264
- package/tests/unit/services/openai-service.test.ts +0 -196
- package/tests/unit/utils/ai-prompts.test.ts +0 -213
- package/tests/unit/utils/file-templates.test.ts +0 -199
- package/tests/unit/utils/naming-conventions.test.ts +0 -88
- package/tests/unit/utils/pdf-to-image.test.ts +0 -127
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -30
|
@@ -1,514 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { promises as fs } from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { FileRenamer } from '../../../src/services/file-renamer.js';
|
|
5
|
-
import { DocumentParserFactory } from '../../../src/parsers/factory.js';
|
|
6
|
-
import { MockAIService } from '../../mocks/mock-ai-service.js';
|
|
7
|
-
import { Config, FileInfo } from '../../../src/types/index.js';
|
|
8
|
-
|
|
9
|
-
// Mock fs.rename to avoid actual file operations
|
|
10
|
-
vi.mock('fs', async () => {
|
|
11
|
-
const actual = await vi.importActual('fs');
|
|
12
|
-
return {
|
|
13
|
-
...actual,
|
|
14
|
-
promises: {
|
|
15
|
-
...actual.promises,
|
|
16
|
-
rename: vi.fn(),
|
|
17
|
-
access: vi.fn()
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe('FileRenamer', () => {
|
|
23
|
-
let fileRenamer: FileRenamer;
|
|
24
|
-
let mockAIService: MockAIService;
|
|
25
|
-
let parserFactory: DocumentParserFactory;
|
|
26
|
-
let config: Config;
|
|
27
|
-
const testDataDir = path.join(process.cwd(), 'tests/data');
|
|
28
|
-
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
mockAIService = new MockAIService();
|
|
31
|
-
parserFactory = new DocumentParserFactory();
|
|
32
|
-
config = {
|
|
33
|
-
aiProvider: 'claude',
|
|
34
|
-
apiKey: 'test-key',
|
|
35
|
-
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
36
|
-
supportedExtensions: ['.txt', '.pdf', '.docx', '.xlsx'],
|
|
37
|
-
dryRun: false,
|
|
38
|
-
namingConvention: 'kebab-case',
|
|
39
|
-
templateOptions: {
|
|
40
|
-
category: 'general',
|
|
41
|
-
personalName: undefined,
|
|
42
|
-
dateFormat: 'none'
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
fileRenamer = new FileRenamer(parserFactory, mockAIService, config);
|
|
47
|
-
|
|
48
|
-
// Reset mocks
|
|
49
|
-
vi.clearAllMocks();
|
|
50
|
-
mockAIService.resetCallCount();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
afterEach(() => {
|
|
54
|
-
vi.restoreAllMocks();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('renameFiles()', () => {
|
|
58
|
-
it('should successfully rename files', async () => {
|
|
59
|
-
const testFiles: FileInfo[] = [
|
|
60
|
-
{
|
|
61
|
-
path: path.join(testDataDir, 'sample-text.txt'),
|
|
62
|
-
name: 'sample-text.txt',
|
|
63
|
-
extension: '.txt',
|
|
64
|
-
size: 1000
|
|
65
|
-
}
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
// Mock fs.access to simulate that new file doesn't exist
|
|
69
|
-
vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
|
|
70
|
-
vi.mocked(fs.rename).mockResolvedValue(undefined);
|
|
71
|
-
|
|
72
|
-
const results = await fileRenamer.renameFiles(testFiles);
|
|
73
|
-
|
|
74
|
-
expect(results).toHaveLength(1);
|
|
75
|
-
expect(results[0].success).toBe(true);
|
|
76
|
-
expect(results[0].originalPath).toBe(testFiles[0].path);
|
|
77
|
-
expect(results[0].newPath).toContain('project-requirements-document.txt');
|
|
78
|
-
expect(mockAIService.getCallCount()).toBe(1);
|
|
79
|
-
expect(fs.rename).toHaveBeenCalledOnce();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should handle dry run mode', async () => {
|
|
83
|
-
config.dryRun = true;
|
|
84
|
-
fileRenamer = new FileRenamer(parserFactory, mockAIService, config);
|
|
85
|
-
|
|
86
|
-
const testFiles: FileInfo[] = [
|
|
87
|
-
{
|
|
88
|
-
path: path.join(testDataDir, 'sample-text.txt'),
|
|
89
|
-
name: 'sample-text.txt',
|
|
90
|
-
extension: '.txt',
|
|
91
|
-
size: 1000
|
|
92
|
-
}
|
|
93
|
-
];
|
|
94
|
-
|
|
95
|
-
// Mock fs.access to simulate that new file doesn't exist
|
|
96
|
-
vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
|
|
97
|
-
|
|
98
|
-
const results = await fileRenamer.renameFiles(testFiles);
|
|
99
|
-
|
|
100
|
-
expect(results).toHaveLength(1);
|
|
101
|
-
expect(results[0].success).toBe(true);
|
|
102
|
-
expect(mockAIService.getCallCount()).toBe(1);
|
|
103
|
-
expect(fs.rename).not.toHaveBeenCalled(); // Should not rename in dry run
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should handle file size limits', async () => {
|
|
107
|
-
config.maxFileSize = 100; // Very small limit
|
|
108
|
-
fileRenamer = new FileRenamer(parserFactory, mockAIService, config);
|
|
109
|
-
|
|
110
|
-
const testFiles: FileInfo[] = [
|
|
111
|
-
{
|
|
112
|
-
path: path.join(testDataDir, 'sample-text.txt'),
|
|
113
|
-
name: 'sample-text.txt',
|
|
114
|
-
extension: '.txt',
|
|
115
|
-
size: 1000 // Exceeds limit
|
|
116
|
-
}
|
|
117
|
-
];
|
|
118
|
-
|
|
119
|
-
const results = await fileRenamer.renameFiles(testFiles);
|
|
120
|
-
|
|
121
|
-
expect(results).toHaveLength(1);
|
|
122
|
-
expect(results[0].success).toBe(false);
|
|
123
|
-
expect(results[0].error).toContain('File size');
|
|
124
|
-
expect(results[0].error).toContain('exceeds maximum');
|
|
125
|
-
expect(mockAIService.getCallCount()).toBe(0); // Should not call AI
|
|
126
|
-
expect(fs.rename).not.toHaveBeenCalled();
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should handle unsupported file types', async () => {
|
|
130
|
-
const testFiles: FileInfo[] = [
|
|
131
|
-
{
|
|
132
|
-
path: path.join(testDataDir, 'unsupported.xyz'),
|
|
133
|
-
name: 'unsupported.xyz',
|
|
134
|
-
extension: '.xyz',
|
|
135
|
-
size: 1000
|
|
136
|
-
}
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
const results = await fileRenamer.renameFiles(testFiles);
|
|
140
|
-
|
|
141
|
-
expect(results).toHaveLength(1);
|
|
142
|
-
expect(results[0].success).toBe(false);
|
|
143
|
-
expect(results[0].error).toContain('No parser available');
|
|
144
|
-
expect(mockAIService.getCallCount()).toBe(0);
|
|
145
|
-
expect(fs.rename).not.toHaveBeenCalled();
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should handle AI service failures', async () => {
|
|
149
|
-
mockAIService.setShouldFail(true);
|
|
150
|
-
|
|
151
|
-
const testFiles: FileInfo[] = [
|
|
152
|
-
{
|
|
153
|
-
path: path.join(testDataDir, 'sample-text.txt'),
|
|
154
|
-
name: 'sample-text.txt',
|
|
155
|
-
extension: '.txt',
|
|
156
|
-
size: 1000
|
|
157
|
-
}
|
|
158
|
-
];
|
|
159
|
-
|
|
160
|
-
const results = await fileRenamer.renameFiles(testFiles);
|
|
161
|
-
|
|
162
|
-
expect(results).toHaveLength(1);
|
|
163
|
-
expect(results[0].success).toBe(false);
|
|
164
|
-
expect(results[0].error).toContain('Mock AI service failed');
|
|
165
|
-
expect(mockAIService.getCallCount()).toBe(1);
|
|
166
|
-
expect(fs.rename).not.toHaveBeenCalled();
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('should handle file conflicts', async () => {
|
|
170
|
-
const testFiles: FileInfo[] = [
|
|
171
|
-
{
|
|
172
|
-
path: path.join(testDataDir, 'sample-text.txt'),
|
|
173
|
-
name: 'sample-text.txt',
|
|
174
|
-
extension: '.txt',
|
|
175
|
-
size: 1000
|
|
176
|
-
}
|
|
177
|
-
];
|
|
178
|
-
|
|
179
|
-
// Mock fs.access to simulate that new file already exists
|
|
180
|
-
vi.mocked(fs.access).mockResolvedValue(undefined);
|
|
181
|
-
|
|
182
|
-
const results = await fileRenamer.renameFiles(testFiles);
|
|
183
|
-
|
|
184
|
-
expect(results).toHaveLength(1);
|
|
185
|
-
expect(results[0].success).toBe(false);
|
|
186
|
-
expect(results[0].error).toContain('Target filename already exists');
|
|
187
|
-
expect(mockAIService.getCallCount()).toBe(1);
|
|
188
|
-
expect(fs.rename).not.toHaveBeenCalled();
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('should handle multiple files', async () => {
|
|
192
|
-
const testFiles: FileInfo[] = [
|
|
193
|
-
{
|
|
194
|
-
path: path.join(testDataDir, 'sample-text.txt'),
|
|
195
|
-
name: 'sample-text.txt',
|
|
196
|
-
extension: '.txt',
|
|
197
|
-
size: 1000
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
path: path.join(testDataDir, 'sample-markdown.md'),
|
|
201
|
-
name: 'sample-markdown.md',
|
|
202
|
-
extension: '.md',
|
|
203
|
-
size: 500
|
|
204
|
-
}
|
|
205
|
-
];
|
|
206
|
-
|
|
207
|
-
vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
|
|
208
|
-
vi.mocked(fs.rename).mockResolvedValue(undefined);
|
|
209
|
-
|
|
210
|
-
const results = await fileRenamer.renameFiles(testFiles);
|
|
211
|
-
|
|
212
|
-
expect(results).toHaveLength(2);
|
|
213
|
-
expect(results.every(r => r.success)).toBe(true);
|
|
214
|
-
expect(mockAIService.getCallCount()).toBe(2);
|
|
215
|
-
expect(fs.rename).toHaveBeenCalledTimes(2);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('should handle empty file content', async () => {
|
|
219
|
-
const testFiles: FileInfo[] = [
|
|
220
|
-
{
|
|
221
|
-
path: path.join(testDataDir, 'empty-file.txt'),
|
|
222
|
-
name: 'empty-file.txt',
|
|
223
|
-
extension: '.txt',
|
|
224
|
-
size: 0
|
|
225
|
-
}
|
|
226
|
-
];
|
|
227
|
-
|
|
228
|
-
const results = await fileRenamer.renameFiles(testFiles);
|
|
229
|
-
|
|
230
|
-
expect(results).toHaveLength(1);
|
|
231
|
-
expect(results[0].success).toBe(false);
|
|
232
|
-
expect(results[0].error).toContain('No content could be extracted');
|
|
233
|
-
expect(mockAIService.getCallCount()).toBe(0);
|
|
234
|
-
expect(fs.rename).not.toHaveBeenCalled();
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('should not rename if filename would be the same', async () => {
|
|
238
|
-
// Set up mock to return a name that would result in the same filename
|
|
239
|
-
mockAIService.setMockResponse('default', 'sample-text');
|
|
240
|
-
|
|
241
|
-
const testFiles: FileInfo[] = [
|
|
242
|
-
{
|
|
243
|
-
path: path.join(testDataDir, 'sample-text.txt'),
|
|
244
|
-
name: 'sample-text.txt',
|
|
245
|
-
extension: '.txt',
|
|
246
|
-
size: 1000
|
|
247
|
-
}
|
|
248
|
-
];
|
|
249
|
-
|
|
250
|
-
vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
|
|
251
|
-
|
|
252
|
-
const results = await fileRenamer.renameFiles(testFiles);
|
|
253
|
-
|
|
254
|
-
expect(results).toHaveLength(1);
|
|
255
|
-
expect(results[0].success).toBe(true);
|
|
256
|
-
expect(results[0].originalPath).toBe(results[0].newPath); // Same path
|
|
257
|
-
expect(mockAIService.getCallCount()).toBe(1);
|
|
258
|
-
expect(fs.rename).not.toHaveBeenCalled(); // No rename needed
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it('should pass naming convention to AI service', async () => {
|
|
262
|
-
config.namingConvention = 'snake_case';
|
|
263
|
-
fileRenamer = new FileRenamer(parserFactory, mockAIService, config);
|
|
264
|
-
|
|
265
|
-
// Spy on the generateFileName method
|
|
266
|
-
const generateFileNameSpy = vi.spyOn(mockAIService, 'generateFileName');
|
|
267
|
-
|
|
268
|
-
const testFiles: FileInfo[] = [
|
|
269
|
-
{
|
|
270
|
-
path: path.join(testDataDir, 'sample-text.txt'),
|
|
271
|
-
name: 'sample-text.txt',
|
|
272
|
-
extension: '.txt',
|
|
273
|
-
size: 1000
|
|
274
|
-
}
|
|
275
|
-
];
|
|
276
|
-
|
|
277
|
-
// Mock fs.access to simulate that new file doesn't exist
|
|
278
|
-
vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
|
|
279
|
-
|
|
280
|
-
const results = await fileRenamer.renameFiles(testFiles);
|
|
281
|
-
|
|
282
|
-
expect(results).toHaveLength(1);
|
|
283
|
-
expect(results[0].success).toBe(true);
|
|
284
|
-
|
|
285
|
-
// Verify AI service was called with the naming convention, category, and file info
|
|
286
|
-
expect(generateFileNameSpy).toHaveBeenCalledWith(
|
|
287
|
-
expect.any(String),
|
|
288
|
-
'sample-text.txt',
|
|
289
|
-
'snake_case',
|
|
290
|
-
'general', // Uses general template since that's the default (no auto-categorization)
|
|
291
|
-
expect.objectContaining({
|
|
292
|
-
name: 'sample-text.txt',
|
|
293
|
-
extension: '.txt',
|
|
294
|
-
documentMetadata: expect.any(Object)
|
|
295
|
-
})
|
|
296
|
-
);
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
describe('Console Output', () => {
|
|
301
|
-
let originalStdoutWrite: typeof process.stdout.write;
|
|
302
|
-
let stdoutOutput: string[];
|
|
303
|
-
|
|
304
|
-
beforeEach(() => {
|
|
305
|
-
stdoutOutput = [];
|
|
306
|
-
originalStdoutWrite = process.stdout.write;
|
|
307
|
-
|
|
308
|
-
// Mock process.stdout.write to capture output
|
|
309
|
-
process.stdout.write = vi.fn((chunk: any) => {
|
|
310
|
-
if (typeof chunk === 'string') {
|
|
311
|
-
stdoutOutput.push(chunk);
|
|
312
|
-
}
|
|
313
|
-
return true;
|
|
314
|
-
}) as any;
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
afterEach(() => {
|
|
318
|
-
process.stdout.write = originalStdoutWrite;
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('should display progress messages during processing', async () => {
|
|
322
|
-
const testFiles: FileInfo[] = [
|
|
323
|
-
{
|
|
324
|
-
path: path.join(testDataDir, 'file1.txt'),
|
|
325
|
-
name: 'file1.txt',
|
|
326
|
-
extension: '.txt',
|
|
327
|
-
size: 1000
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
path: path.join(testDataDir, 'file2.txt'),
|
|
331
|
-
name: 'file2.txt',
|
|
332
|
-
extension: '.txt',
|
|
333
|
-
size: 1000
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
path: path.join(testDataDir, 'very-long-filename-that-should-be-cleared-properly.txt'),
|
|
337
|
-
name: 'very-long-filename-that-should-be-cleared-properly.txt',
|
|
338
|
-
extension: '.txt',
|
|
339
|
-
size: 1000
|
|
340
|
-
}
|
|
341
|
-
];
|
|
342
|
-
|
|
343
|
-
// Mock fs.access to simulate that new file doesn't exist
|
|
344
|
-
vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
|
|
345
|
-
vi.mocked(fs.rename).mockResolvedValue(undefined);
|
|
346
|
-
|
|
347
|
-
await fileRenamer.renameFiles(testFiles);
|
|
348
|
-
|
|
349
|
-
// Check that progress messages were written
|
|
350
|
-
const outputString = stdoutOutput.join('');
|
|
351
|
-
expect(outputString).toContain('🔄 Processing [1/3] file1.txt');
|
|
352
|
-
expect(outputString).toContain('🔄 Processing [2/3] file2.txt');
|
|
353
|
-
expect(outputString).toContain('🔄 Processing [3/3] very-long-filename-that-should-be-cleared-prope...');
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it('should properly clear previous progress lines', async () => {
|
|
357
|
-
const testFiles: FileInfo[] = [
|
|
358
|
-
{
|
|
359
|
-
path: path.join(testDataDir, 'short.txt'),
|
|
360
|
-
name: 'short.txt',
|
|
361
|
-
extension: '.txt',
|
|
362
|
-
size: 1000
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
path: path.join(testDataDir, 'much-longer-filename-to-test-clearing.txt'),
|
|
366
|
-
name: 'much-longer-filename-to-test-clearing.txt',
|
|
367
|
-
extension: '.txt',
|
|
368
|
-
size: 1000
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
path: path.join(testDataDir, 'x.txt'),
|
|
372
|
-
name: 'x.txt',
|
|
373
|
-
extension: '.txt',
|
|
374
|
-
size: 1000
|
|
375
|
-
}
|
|
376
|
-
];
|
|
377
|
-
|
|
378
|
-
// Mock fs.access to simulate that new file doesn't exist
|
|
379
|
-
vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
|
|
380
|
-
vi.mocked(fs.rename).mockResolvedValue(undefined);
|
|
381
|
-
|
|
382
|
-
await fileRenamer.renameFiles(testFiles);
|
|
383
|
-
|
|
384
|
-
// Should contain clearing sequences (spaces to overwrite previous content)
|
|
385
|
-
const outputString = stdoutOutput.join('');
|
|
386
|
-
|
|
387
|
-
// Should contain carriage returns and spaces for clearing
|
|
388
|
-
expect(outputString).toContain('\r');
|
|
389
|
-
expect(outputString).toMatch(/\s+/); // Should contain spaces for clearing
|
|
390
|
-
|
|
391
|
-
// Final clear should happen at the end
|
|
392
|
-
const lastOutputs = stdoutOutput.slice(-3);
|
|
393
|
-
expect(lastOutputs.some(output => output.includes('\r') && output.includes(' '))).toBe(true);
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it('should handle single file processing correctly', async () => {
|
|
397
|
-
const testFiles: FileInfo[] = [
|
|
398
|
-
{
|
|
399
|
-
path: path.join(testDataDir, 'single-file.txt'),
|
|
400
|
-
name: 'single-file.txt',
|
|
401
|
-
extension: '.txt',
|
|
402
|
-
size: 1000
|
|
403
|
-
}
|
|
404
|
-
];
|
|
405
|
-
|
|
406
|
-
// Mock fs.access to simulate that new file doesn't exist
|
|
407
|
-
vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
|
|
408
|
-
vi.mocked(fs.rename).mockResolvedValue(undefined);
|
|
409
|
-
|
|
410
|
-
await fileRenamer.renameFiles(testFiles);
|
|
411
|
-
|
|
412
|
-
const outputString = stdoutOutput.join('');
|
|
413
|
-
expect(outputString).toContain('🔄 Processing [1/1] single-file.txt');
|
|
414
|
-
|
|
415
|
-
// Should still clear the line at the end
|
|
416
|
-
expect(outputString).toContain('\r');
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
it('should handle empty file list without console output', async () => {
|
|
420
|
-
const testFiles: FileInfo[] = [];
|
|
421
|
-
|
|
422
|
-
await fileRenamer.renameFiles(testFiles);
|
|
423
|
-
|
|
424
|
-
// With no files, there should be minimal or no output
|
|
425
|
-
expect(stdoutOutput.length).toBeLessThan(3);
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
it('should show completion message after processing', async () => {
|
|
429
|
-
const testFiles: FileInfo[] = [
|
|
430
|
-
{
|
|
431
|
-
path: path.join(testDataDir, 'file1.txt'),
|
|
432
|
-
name: 'file1.txt',
|
|
433
|
-
extension: '.txt',
|
|
434
|
-
size: 1000
|
|
435
|
-
},
|
|
436
|
-
{
|
|
437
|
-
path: path.join(testDataDir, 'file2.txt'),
|
|
438
|
-
name: 'file2.txt',
|
|
439
|
-
extension: '.txt',
|
|
440
|
-
size: 1000
|
|
441
|
-
}
|
|
442
|
-
];
|
|
443
|
-
|
|
444
|
-
// Mock fs.access to simulate that new file doesn't exist
|
|
445
|
-
vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
|
|
446
|
-
vi.mocked(fs.rename).mockResolvedValue(undefined);
|
|
447
|
-
|
|
448
|
-
await fileRenamer.renameFiles(testFiles);
|
|
449
|
-
|
|
450
|
-
const outputString = stdoutOutput.join('');
|
|
451
|
-
expect(outputString).toContain('✅ Processed 2 files (2 successful)');
|
|
452
|
-
expect(outputString).toContain('\n'); // Should end with newline
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
it('should truncate very long filenames in progress display', async () => {
|
|
456
|
-
const longFilename = 'this-is-a-very-long-filename-that-should-be-truncated-for-better-display-purposes.txt';
|
|
457
|
-
const testFiles: FileInfo[] = [
|
|
458
|
-
{
|
|
459
|
-
path: path.join(testDataDir, longFilename),
|
|
460
|
-
name: longFilename,
|
|
461
|
-
extension: '.txt',
|
|
462
|
-
size: 1000
|
|
463
|
-
}
|
|
464
|
-
];
|
|
465
|
-
|
|
466
|
-
// Mock fs.access to simulate that new file doesn't exist
|
|
467
|
-
vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
|
|
468
|
-
vi.mocked(fs.rename).mockResolvedValue(undefined);
|
|
469
|
-
|
|
470
|
-
await fileRenamer.renameFiles(testFiles);
|
|
471
|
-
|
|
472
|
-
const outputString = stdoutOutput.join('');
|
|
473
|
-
expect(outputString).toContain('🔄 Processing [1/1] this-is-a-very-long-filename-that-should-be-tru...');
|
|
474
|
-
expect(outputString).not.toContain(longFilename); // Full name should not appear
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
it('should handle mixed success/failure results in completion message', async () => {
|
|
478
|
-
const testFiles: FileInfo[] = [
|
|
479
|
-
{
|
|
480
|
-
path: path.join(testDataDir, 'success.txt'),
|
|
481
|
-
name: 'success.txt',
|
|
482
|
-
extension: '.txt',
|
|
483
|
-
size: 1000
|
|
484
|
-
},
|
|
485
|
-
{
|
|
486
|
-
path: path.join(testDataDir, 'failure.txt'),
|
|
487
|
-
name: 'failure.txt',
|
|
488
|
-
extension: '.txt',
|
|
489
|
-
size: 1000
|
|
490
|
-
}
|
|
491
|
-
];
|
|
492
|
-
|
|
493
|
-
// Mock the first file to succeed and second to fail
|
|
494
|
-
vi.mocked(fs.access).mockImplementation((path: string) => {
|
|
495
|
-
if (path.includes('success.txt')) {
|
|
496
|
-
return Promise.reject({ code: 'ENOENT' }); // File doesn't exist, rename will succeed
|
|
497
|
-
}
|
|
498
|
-
return Promise.reject({ code: 'ENOENT' }); // File doesn't exist, rename will succeed
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
vi.mocked(fs.rename).mockImplementation((oldPath: string) => {
|
|
502
|
-
if (oldPath.includes('failure.txt')) {
|
|
503
|
-
return Promise.reject(new Error('Permission denied'));
|
|
504
|
-
}
|
|
505
|
-
return Promise.resolve(undefined);
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
await fileRenamer.renameFiles(testFiles);
|
|
509
|
-
|
|
510
|
-
const outputString = stdoutOutput.join('');
|
|
511
|
-
expect(outputString).toContain('✅ Processed 2 files (1 successful)');
|
|
512
|
-
});
|
|
513
|
-
});
|
|
514
|
-
});
|