Asper Header  1.0.14
The header injector extension
Loading...
Searching...
No Matches
commentGenerator.test.ts
Go to the documentation of this file.
1
14import * as assert from 'assert';
15import * as vscode from 'vscode';
16import * as fs from 'fs/promises';
17import * as path from 'path';
18import { CommentGenerator } from '../modules/commentGenerator';
19import { LazyFileLoader } from '../modules/lazyFileLoad';
20import { RandomLogo } from '../modules/randomLogo';
21import { CodeConfig } from '../modules/processConfiguration';
22
23// Mock state for VS Code APIs
24let mockActiveTextEditor: vscode.TextEditor | undefined = undefined;
25let mockShowInputBoxResponse: string | undefined = undefined;
26let mockShowQuickPickResponse: string | undefined = undefined;
27let mockEditOperations: Array<{ position: vscode.Position; text: string; isInsert: boolean; range?: vscode.Range }> = [];
28
37interface MockDocumentLine {
39 text: string;
41 range: vscode.Range;
43 lineNumber: number;
45 rangeIncludingLineBreak: vscode.Range;
47 firstNonWhitespaceCharacterIndex: number;
49 isEmptyOrWhitespace: boolean;
50}
51
69class MockTextDocument implements Partial<vscode.TextDocument> {
71 public lines: MockDocumentLine[] = [];
73 public uri: vscode.Uri;
75 public languageId: string;
77 public eol: vscode.EndOfLine;
79 public version: number;
81 public isClosed: boolean;
83 public lineCount: number = 0;
85 public fileName: string = '';
87 public isUntitled: boolean = false;
89 public encoding: string = 'utf8';
91 public isDirty: boolean = false;
92
106 filePath: string,
107 content: string = '',
108 languageId: string = 'typescript',
109 eol: vscode.EndOfLine = vscode.EndOfLine.LF,
110 isClosed: boolean = false
111 ) {
112 this.uri = vscode.Uri.file(filePath);
113 this.languageId = languageId;
114 this.eol = eol;
115 this.version = 1;
116 this.isClosed = isClosed;
117
118 this.setContent(content);
119 }
120
131 setContent(content: string): void {
132 const lines = content.split('\n');
133 this.lines = lines.map((text, index) => ({
134 text,
135 range: new vscode.Range(index, 0, index, text.length),
136 lineNumber: index,
137 rangeIncludingLineBreak: new vscode.Range(index, 0, index + 1, 0),
138 firstNonWhitespaceCharacterIndex: text.search(/\S/),
139 isEmptyOrWhitespace: text.trim().length === 0
140 }));
141 this.lineCount = this.lines.length;
142 this.fileName = path.basename(this.uri.fsPath);
143 }
144
151 lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine {
152 const line = typeof lineOrPosition === 'number' ? lineOrPosition : lineOrPosition.line;
153 if (line < 0 || line >= this.lines.length) {
154 throw new Error(`Line ${line} is out of range`);
155 }
156 return this.lines[line] as vscode.TextLine;
157 }
158
164 offsetAt(position: vscode.Position): number {
165 let offset = 0;
166 for (let i = 0; i < position.line && i < this.lines.length; i++) {
167 offset += this.lines[i].text.length + 1; // +1 for newline
168 }
169 return offset + Math.min(position.character, this.lines[position.line]?.text.length || 0);
170 }
171
177 positionAt(offset: number): vscode.Position {
178 let currentOffset = 0;
179 for (let line = 0; line < this.lines.length; line++) {
180 const lineLength = this.lines[line].text.length + 1; // +1 for newline
181 if (currentOffset + lineLength > offset) {
182 return new vscode.Position(line, offset - currentOffset);
183 }
184 currentOffset += lineLength;
185 }
186 return new vscode.Position(this.lines.length - 1, (this.lines[this.lines.length - 1]?.text.length || 0));
187 }
188
194 getText(range?: vscode.Range): string {
195 if (!range) {
196 return this.lines.map(line => line.text).join('\n');
197 }
198
199 if (range.start.line === range.end.line) {
200 const line = this.lines[range.start.line];
201 return line ? line.text.substring(range.start.character, range.end.character) : '';
202 }
203
204 let result = '';
205 for (let i = range.start.line; i <= range.end.line && i < this.lines.length; i++) {
206 const line = this.lines[i];
207 if (!line) { continue; }
208
209 if (i === range.start.line) {
210 result += line.text.substring(range.start.character);
211 } else if (i === range.end.line) {
212 result += line.text.substring(0, range.end.character);
213 } else {
214 result += line.text;
215 }
216
217 if (i < range.end.line) {
218 result += '\n';
219 }
220 }
221 return result;
222 }
223
229 validateRange(range: vscode.Range): vscode.Range {
230 return range;
231 }
232
238 validatePosition(position: vscode.Position): vscode.Position {
239 return position;
240 }
241
248 getWordRangeAtPosition(position: vscode.Position, regex?: RegExp): vscode.Range | undefined {
249 const line = this.lines[position.line];
250 if (!line) { return undefined; }
251
252 const wordRegex = regex || /[\w]+/g;
253 let match;
254 while ((match = wordRegex.exec(line.text)) !== null) {
255 if (match.index <= position.character && match.index + match[0].length >= position.character) {
256 return new vscode.Range(position.line, match.index, position.line, match.index + match[0].length);
257 }
258 }
259 return undefined;
260 }
261
266 save(): Thenable<boolean> {
267 return Promise.resolve(true);
268 }
269}
270
284class MockTextEditor implements Partial<vscode.TextEditor> {
286 public document: vscode.TextDocument;
287
296 this.document = document as unknown as vscode.TextDocument;
297 }
298
299 async edit(callback: (editBuilder: vscode.TextEditorEdit) => void): Promise<boolean> {
300 const mockEditBuilder = {
301 insert: (location: vscode.Position, value: string) => {
302 mockEditOperations.push({
303 position: location,
304 text: value,
305 isInsert: true
306 });
307 },
308 replace: (location: vscode.Range, value: string) => {
309 mockEditOperations.push({
310 position: location.start,
311 text: value,
312 isInsert: false,
313 range: location
314 });
315 }
316 };
317
318 callback(mockEditBuilder as vscode.TextEditorEdit);
319 return true;
320 }
321}
322
323// Store original VS Code methods
327
334suite('CommentGenerator Test Suite', function () {
336 let tempDir: string;
338 let languageConfigFile: string;
340 let lazyFileLoader: LazyFileLoader;
342 let mockRandomLogo: RandomLogo;
344 let generator: CommentGenerator;
345
354 setup(async () => {
355 // Create temporary directory for test files
356 tempDir = await fs.mkdtemp(path.join(require('os').tmpdir(), 'commentgen-test-'));
357
358 // Create language configuration file
359 languageConfigFile = path.join(tempDir, 'languages.json');
360 const languageConfig = {
361 langs: [
362 {
363 langs: ['typescript', 'javascript'],
364 fileExtensions: {
365 typescript: ['.ts', '.tsx'],
366 javascript: ['.js', '.jsx']
367 },
368 singleLine: ['//'],
369 multiLine: ['/*', ' *', ' */'],
370 prompt_comment_opening_type: false
371 },
372 {
373 langs: ['python'],
374 fileExtensions: {
375 python: ['.py']
376 },
377 singleLine: ['#'],
378 multiLine: [],
379 prompt_comment_opening_type: false
380 },
381 {
382 langs: ['c', 'cpp'],
383 fileExtensions: {
384 c: ['.c', '.h'],
385 cpp: ['.cpp', '.hpp', '.cxx']
386 },
387 singleLine: ['//'],
388 multiLine: ['/*', ' *', ' */'],
389 prompt_comment_opening_type: true
390 }
391 ]
392 };
393 await fs.writeFile(languageConfigFile, JSON.stringify(languageConfig, null, 2));
394
395 // Create lazy file loader
396 lazyFileLoader = new LazyFileLoader(languageConfigFile, tempDir);
397
398 // Create mock random logo
399 mockRandomLogo = new RandomLogo();
400
401 // Reset mock state
402 mockActiveTextEditor = undefined;
403 mockShowInputBoxResponse = undefined;
404 mockShowQuickPickResponse = undefined;
406
407 // Store original VS Code methods
408 originalActiveTextEditor = vscode.window.activeTextEditor;
409 originalShowInputBox = vscode.window.showInputBox;
410 originalShowQuickPick = vscode.window.showQuickPick;
411
412 // Mock VS Code APIs
413 Object.defineProperty(vscode.window, 'activeTextEditor', {
414 get: () => mockActiveTextEditor,
415 configurable: true
416 });
417
418 (vscode.window as any).showInputBox = async (options?: vscode.InputBoxOptions) => {
420 };
421
422 (vscode.window as any).showQuickPick = async (items: string[], options?: vscode.QuickPickOptions) => {
424 };
425
426 // Reset CodeConfig to default values to ensure clean state
427 // This prevents configuration changes from other test suites from affecting these tests
428 await CodeConfig.refreshVariables();
429 });
430
438 teardown(async () => {
439 // Cleanup temporary directory
440 try {
441 await fs.rm(tempDir, { recursive: true, force: true });
442 } catch (error) {
443 // Ignore cleanup errors
444 }
445
446 // Restore original VS Code methods
447 Object.defineProperty(vscode.window, 'activeTextEditor', {
448 value: originalActiveTextEditor,
449 configurable: true
450 });
451 (vscode.window as any).showInputBox = originalShowInputBox;
452 (vscode.window as any).showQuickPick = originalShowQuickPick;
453 });
454
461 suite('Constructor and Initialization', () => {
466 test('should create instance with all parameters provided', () => {
467 const document = new MockTextDocument('/test/file.ts');
468 const editor = new MockTextEditor(document);
469
470 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
471
472 assert.ok(generator, 'Generator should be created successfully');
473 });
474
479 test('should create instance with minimal parameters', () => {
480 generator = new CommentGenerator();
481
482 assert.ok(generator, 'Generator should be created with undefined parameters');
483 });
484
489 test('should create instance with only language loader', () => {
490 generator = new CommentGenerator(lazyFileLoader);
491
492 assert.ok(generator, 'Generator should be created with language loader only');
493 });
494
499 test('should handle undefined editor gracefully', () => {
500 generator = new CommentGenerator(lazyFileLoader, undefined, mockRandomLogo);
501
502 assert.ok(generator, 'Generator should handle undefined editor');
503 });
504 });
505
513 suite('File Information Processing', () => {
518 test('should extract correct file metadata from TypeScript editor', () => {
519 const filePath = '/home/user/project/test.ts';
520 const document = new MockTextDocument(filePath, '', 'typescript');
521 const editor = new MockTextEditor(document);
522
523 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
524
525 // Access private properties through type assertion
526 const generatorAny = generator as any;
527 assert.strictEqual(generatorAny.fileName, 'test.ts');
528 assert.strictEqual(generatorAny.fileExtension, 'ts');
529 assert.strictEqual(generatorAny.languageId, 'typescript');
530 assert.strictEqual(generatorAny.filePath, filePath);
531 });
532
537 test('should extract correct file metadata from Python editor', () => {
538 const filePath = '/home/user/project/script.py';
539 const document = new MockTextDocument(filePath, '', 'python');
540 const editor = new MockTextEditor(document);
541
542 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
543
544 const generatorAny = generator as any;
545 assert.strictEqual(generatorAny.fileName, 'script.py');
546 assert.strictEqual(generatorAny.fileExtension, 'py');
547 assert.strictEqual(generatorAny.languageId, 'python');
548 });
549
554 test('should handle files without extensions', () => {
555 const filePath = '/home/user/Makefile';
556 const document = new MockTextDocument(filePath, '', 'makefile');
557 const editor = new MockTextEditor(document);
558
559 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
560
561 const generatorAny = generator as any;
562 assert.strictEqual(generatorAny.fileName, 'Makefile');
563 assert.strictEqual(generatorAny.fileExtension, 'none');
564 });
565
570 test('should handle different EOL types', () => {
571 const document = new MockTextDocument('/test/file.ts', '', 'typescript', vscode.EndOfLine.CRLF);
572 const editor = new MockTextEditor(document);
573
574 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
575
576 const generatorAny = generator as any;
577 assert.strictEqual(generatorAny.documentEOL, vscode.EndOfLine.CRLF);
578 });
579 });
580
588 suite('Comment Style Detection', () => {
593 test('should detect TypeScript comment style correctly', async () => {
594 const document = new MockTextDocument('/test/file.ts', '', 'typescript');
595 const editor = new MockTextEditor(document);
596
597 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
598
599 const generatorAny = generator as any;
600 const commentStyle = await generatorAny.determineCorrectComment();
601
602 assert.deepStrictEqual(commentStyle.singleLine, ['//']);
603 assert.deepStrictEqual(commentStyle.multiLine, ['/*', ' *', ' */']);
604 assert.strictEqual(commentStyle.prompt_comment_opening_type, false);
605 });
606
611 test('should detect Python comment style correctly', async () => {
612 const document = new MockTextDocument('/test/script.py', '', 'python');
613 const editor = new MockTextEditor(document);
614
615 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
616
617 const generatorAny = generator as any;
618 const commentStyle = await generatorAny.determineCorrectComment();
619
620 assert.deepStrictEqual(commentStyle.singleLine, ['#']);
621 assert.deepStrictEqual(commentStyle.multiLine, []);
622 });
623
628 test('should detect C++ comment style with prompting', async () => {
629 const document = new MockTextDocument('/test/main.cpp', '', 'cpp');
630 const editor = new MockTextEditor(document);
631
632 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
633
634 const generatorAny = generator as any;
635 const commentStyle = await generatorAny.determineCorrectComment();
636
637 assert.deepStrictEqual(commentStyle.singleLine, ['//']);
638 assert.deepStrictEqual(commentStyle.multiLine, ['/*', ' *', ' */']);
639 assert.strictEqual(commentStyle.prompt_comment_opening_type, true);
640 });
641
646 test('should fallback to file extension matching', async () => {
647 const document = new MockTextDocument('/test/file.tsx', '', 'unknown');
648 const editor = new MockTextEditor(document);
649
650 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
651
652 const generatorAny = generator as any;
653 const commentStyle = await generatorAny.determineCorrectComment();
654
655 // Should match TypeScript config based on .tsx extension
656 assert.deepStrictEqual(commentStyle.singleLine, ['//']);
657 assert.deepStrictEqual(commentStyle.multiLine, ['/*', ' *', ' */']);
658 });
659
664 test('should return empty style for unknown language', async () => {
665 const document = new MockTextDocument('/test/file.xyz', '', 'unknown');
666 const editor = new MockTextEditor(document);
667
668 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
669
670 const generatorAny = generator as any;
671 const commentStyle = await generatorAny.determineCorrectComment();
672
673 assert.deepStrictEqual(commentStyle.singleLine, []);
674 assert.deepStrictEqual(commentStyle.multiLine, []);
675 assert.strictEqual(commentStyle.prompt_comment_opening_type, false);
676 });
677 });
678
686 suite('User Input Handling', () => {
691 test('should get file description from user input', async () => {
692 const document = new MockTextDocument('/test/file.ts');
693 const editor = new MockTextEditor(document);
694 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
695
696 mockShowInputBoxResponse = 'This is a test file for the application';
697
698 const generatorAny = generator as any;
699 const description = await generatorAny.determineHeaderDescription();
700
701 assert.deepStrictEqual(description, ['This is a test file for the application']);
702 });
703
708 test('should handle empty description input', async () => {
709 const document = new MockTextDocument('/test/file.ts');
710 const editor = new MockTextEditor(document);
711 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
712
713 mockShowInputBoxResponse = undefined;
714
715 const generatorAny = generator as any;
716 const description = await generatorAny.determineHeaderDescription();
717
718 assert.deepStrictEqual(description, ['']);
719 });
720
725 test('should get file purpose from user input', async () => {
726 const document = new MockTextDocument('/test/file.ts');
727 const editor = new MockTextEditor(document);
728 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
729
730 mockShowInputBoxResponse = 'Main entry point for the application';
731
732 const generatorAny = generator as any;
733 const purpose = await generatorAny.determineHeaderPurpose();
734
735 assert.deepStrictEqual(purpose, ['Main entry point for the application']);
736 });
737
742 test('should get single comment option without prompting', async () => {
743 const document = new MockTextDocument('/test/file.ts');
744 const editor = new MockTextEditor(document);
745 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
746
747 const generatorAny = generator as any;
748 const result = await generatorAny.getSingleCommentOption(['//']);
749
750 assert.strictEqual(result, '//');
751 });
752
757 test('should prompt for comment selection when multiple options', async () => {
758 const document = new MockTextDocument('/test/file.ts');
759 const editor = new MockTextEditor(document);
760 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
761
763
764 const generatorAny = generator as any;
765 const result = await generatorAny.getSingleCommentOption(['//', '/*']);
766
767 assert.strictEqual(result, '/*');
768 });
769
774 test('should return first option when user cancels selection', async () => {
775 const document = new MockTextDocument('/test/file.ts');
776 const editor = new MockTextEditor(document);
777 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
778
779 mockShowQuickPickResponse = undefined;
780
781 const generatorAny = generator as any;
782 const result = await generatorAny.getSingleCommentOption(['//', '/*']);
783
784 assert.strictEqual(result, '//');
785 });
786
791 test('should throw error for empty comment options', async () => {
792 const document = new MockTextDocument('/test/file.ts');
793 const editor = new MockTextEditor(document);
794 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
795
796 const generatorAny = generator as any;
797
798 await assert.rejects(
799 async () => await generatorAny.getSingleCommentOption([]),
800 Error
801 );
802 });
803 });
804
812 suite('Header Content Generation', () => {
817 test('should generate correct header opener', () => {
818 const document = new MockTextDocument('/test/file.ts');
819 const editor = new MockTextEditor(document);
820 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
821
822 const generatorAny = generator as any;
823 const opener = generatorAny.headerOpener(' * ', vscode.EndOfLine.LF, 'TestProject');
824
825 assert.ok(opener.includes('TestProject'));
826 assert.ok(opener.includes(' * '));
827 assert.ok(opener.endsWith('\n'));
828 });
829
834 test('should generate correct header closer', () => {
835 const document = new MockTextDocument('/test/file.ts');
836 const editor = new MockTextEditor(document);
837 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
838
839 const generatorAny = generator as any;
840 const closer = generatorAny.headerCloser(' * ', vscode.EndOfLine.LF, 'TestProject');
841
842 assert.ok(closer.includes('TestProject'));
843 assert.ok(closer.includes(' * '));
844 assert.ok(closer.endsWith('\n'));
845 });
846
851 test('should generate creation date with correct format', () => {
852 const document = new MockTextDocument('/test/file.ts');
853 const editor = new MockTextEditor(document);
854 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
855
856 const generatorAny = generator as any;
857 const creationDate = generatorAny.addCreationDate(' * ', vscode.EndOfLine.LF);
858
859 assert.ok(creationDate.includes(' * '));
860 assert.ok(creationDate.includes('2025')); // Current year
861 assert.ok(creationDate.endsWith('\n'));
862 });
863
868 test('should generate last modified date with time', () => {
869 const document = new MockTextDocument('/test/file.ts');
870 const editor = new MockTextEditor(document);
871 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
872
873 const generatorAny = generator as any;
874 const modifiedDate = generatorAny.addLastModifiedDate(' * ', vscode.EndOfLine.LF);
875
876 assert.ok(modifiedDate.includes(' * '));
877 assert.ok(modifiedDate.includes('2025')); // Current year
878 assert.ok(modifiedDate.includes(':')); // Time separator
879 assert.ok(modifiedDate.endsWith('\n'));
880 });
881
886 test('should generate single line key-value pair', () => {
887 const document = new MockTextDocument('/test/file.ts');
888 const editor = new MockTextEditor(document);
889 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
890
891 const generatorAny = generator as any;
892 const singleLine = generatorAny.addSingleLineKey(' * ', vscode.EndOfLine.LF, 'Author', 'John Doe');
893
894 assert.ok(singleLine.includes(' * '));
895 assert.ok(singleLine.includes('Author'));
896 assert.ok(singleLine.includes('John Doe'));
897 assert.ok(singleLine.endsWith('\n'));
898 });
899
904 test('should generate multi-line key section', () => {
905 const document = new MockTextDocument('/test/file.ts');
906 const editor = new MockTextEditor(document);
907 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
908
909 const generatorAny = generator as any;
910 const multiLine = generatorAny.addMultilineKey(' * ', vscode.EndOfLine.LF, 'Description', ['Line 1', 'Line 2']);
911
912 assert.ok(multiLine.includes(' * '));
913 assert.ok(multiLine.includes('Description'));
914 assert.ok(multiLine.includes('Line 1'));
915 assert.ok(multiLine.includes('Line 2'));
916 });
917
922 test('should handle CRLF line endings correctly', () => {
923 const document = new MockTextDocument('/test/file.ts', '', 'typescript', vscode.EndOfLine.CRLF);
924 const editor = new MockTextEditor(document);
925 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
926
927 const generatorAny = generator as any;
928 const eolString = generatorAny.determineNewLine(vscode.EndOfLine.CRLF);
929
930 assert.strictEqual(eolString, '\r\n');
931 });
932
937 test('should handle LF line endings correctly', () => {
938 const document = new MockTextDocument('/test/file.ts');
939 const editor = new MockTextEditor(document);
940 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
941
942 const generatorAny = generator as any;
943 const eolString = generatorAny.determineNewLine(vscode.EndOfLine.LF);
944
945 assert.strictEqual(eolString, '\n');
946 });
947 });
948
956 suite('Comment Prefix Processing', () => {
961 test('should process multi-line comments with three parts', async () => {
962 const document = new MockTextDocument('/test/file.ts');
963 const editor = new MockTextEditor(document);
964 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
965
966 const commentStyle = {
967 singleLine: [],
968 multiLine: ['/*', ' *', ' */'],
969 prompt_comment_opening_type: false
970 };
971
972 const generatorAny = generator as any;
973 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
974
975 assert.strictEqual(prefixes.length, 3);
976 assert.ok(prefixes[0].includes('/*'));
977 assert.ok(prefixes[1].includes(' *'));
978 assert.ok(prefixes[2].includes(' */'));
979 });
980
985 test('should process multi-line comments with two parts', async () => {
986 const document = new MockTextDocument('/test/file.ts');
987 const editor = new MockTextEditor(document);
988 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
989
990 const commentStyle = {
991 singleLine: [],
992 multiLine: ['<!--', '-->'],
993 prompt_comment_opening_type: false
994 };
995
996 const generatorAny = generator as any;
997 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
998
999 assert.strictEqual(prefixes.length, 3);
1000 assert.ok(prefixes[0].includes('<!--'));
1001 assert.strictEqual(prefixes[1].trim(), ''); // Empty middle
1002 assert.ok(prefixes[2].includes('-->'));
1003 });
1004
1009 test('should process single-line comments without prompting', async () => {
1010 const document = new MockTextDocument('/test/file.py');
1011 const editor = new MockTextEditor(document);
1012 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1013
1014 const commentStyle = {
1015 singleLine: ['#'],
1016 multiLine: [],
1017 prompt_comment_opening_type: false
1018 };
1019
1020 const generatorAny = generator as any;
1021 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1022
1023 assert.strictEqual(prefixes.length, 3);
1024 assert.ok(prefixes[0].includes('#'));
1025 assert.ok(prefixes[1].includes('#'));
1026 assert.ok(prefixes[2].includes('#'));
1027 });
1028
1033 test('should prompt for single-line comment selection', async () => {
1034 const document = new MockTextDocument('/test/file.cpp');
1035 const editor = new MockTextEditor(document);
1036 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1037
1039
1040 const commentStyle = {
1041 singleLine: ['//', '#'],
1042 multiLine: [],
1043 prompt_comment_opening_type: true
1044 };
1045
1046 const generatorAny = generator as any;
1047 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1048
1049 assert.strictEqual(prefixes.length, 3);
1050 assert.ok(prefixes[0].includes('//'));
1051 assert.ok(prefixes[1].includes('//'));
1052 assert.ok(prefixes[2].includes('//'));
1053 });
1054
1059 test('should handle empty comment configurations', async () => {
1060 const document = new MockTextDocument('/test/file.unknown');
1061 const editor = new MockTextEditor(document);
1062 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1063
1064 const commentStyle = {
1065 singleLine: [],
1066 multiLine: [],
1067 prompt_comment_opening_type: false
1068 };
1069
1070 const generatorAny = generator as any;
1071 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1072
1073 assert.strictEqual(prefixes.length, 3);
1074 // When no comment styles are available, should still have spacing
1075 assert.strictEqual(prefixes[0], ' '); // Just spacing
1076 assert.strictEqual(prefixes[1], ' ');
1077 assert.ok(prefixes[2].includes(' '));
1078 });
1079 });
1080
1088 suite('Header Detection and Parsing', () => {
1093 test('should detect existing header correctly', () => {
1094 const headerContent = `/*
1095 * +==== BEGIN AsperHeader =================+
1096 * Logo:
1097 * ▄▄▄▄▄▄▄▄
1098 * ───────
1099 * Project: AsperHeader
1100 * File: test.ts
1101 * Created: 03-10-2025
1102 * LAST Modified: 15:30:45 03-10-2025
1103 * Description:
1104 * Test file
1105 * ───────
1106 * Copyright: © 2025
1107 * Purpose: Testing
1108 * +==== END AsperHeader =================+
1109 */
1110
1111const someCode = true;`;
1112
1113 const document = new MockTextDocument('/test/test.ts', headerContent);
1114 const editor = new MockTextEditor(document);
1115 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1116
1117 const generatorAny = generator as any;
1118 const comments = [' * ', ' * ', ' * '];
1119 const hasHeader = generatorAny.locateIfHeaderPresent(comments);
1120
1121 assert.strictEqual(hasHeader, true);
1122 assert.ok(typeof generatorAny.headerInnerStart === 'number');
1123 assert.ok(typeof generatorAny.headerInnerEnd === 'number');
1124 });
1125
1130 test('should detect missing header correctly', () => {
1131 const content = `const someCode = true;
1132function myFunction() {
1133 return 'hello';
1134}`;
1135
1136 const document = new MockTextDocument('/test/test.ts', content);
1137 const editor = new MockTextEditor(document);
1138 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1139
1140 const generatorAny = generator as any;
1141 const comments = [' * ', ' * ', ' * '];
1142 const hasHeader = generatorAny.locateIfHeaderPresent(comments);
1143
1144 assert.strictEqual(hasHeader, false);
1145 assert.strictEqual(generatorAny.headerInnerStart, undefined);
1146 assert.strictEqual(generatorAny.headerInnerEnd, undefined);
1147 });
1148
1153 test('should detect broken header (opener without closer)', () => {
1154 const brokenContent = `/*
1155 * ═══════════════════════ ◄ BEGIN TestProject ► ═══════════════════════
1156 * Project: TestProject
1157 * File: test.ts
1158
1159const someCode = true;`;
1160
1161 const document = new MockTextDocument('/test/test.ts', brokenContent);
1162 const editor = new MockTextEditor(document);
1163 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1164
1165 const generatorAny = generator as any;
1166 const comments = [' * ', ' * ', ' * '];
1167 const hasHeader = generatorAny.locateIfHeaderPresent(comments);
1168
1169 assert.strictEqual(hasHeader, false);
1170 });
1171
1176 test('should detect broken header (closer without opener)', () => {
1177 const brokenContent = `const someCode = true;
1178 * ═══════════════════════ ◄ END TestProject ► ═══════════════════════
1179 */`;
1180
1181 const document = new MockTextDocument('/test/test.ts', brokenContent);
1182 const editor = new MockTextEditor(document);
1183 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1184
1185 const generatorAny = generator as any;
1186 const comments = [' * ', ' * ', ' * '];
1187 const hasHeader = generatorAny.locateIfHeaderPresent(comments);
1188
1189 assert.strictEqual(hasHeader, false);
1190 });
1191
1196 test('should handle closed document gracefully', () => {
1197 const document = new MockTextDocument('/test/test.ts', '', 'typescript', vscode.EndOfLine.LF, true);
1198 const editor = new MockTextEditor(document);
1199 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1200
1201 const generatorAny = generator as any;
1202 const comments = [' * ', ' * ', ' * '];
1203 const hasHeader = generatorAny.locateIfHeaderPresent(comments);
1204
1205 assert.strictEqual(hasHeader, undefined);
1206 });
1207
1212 test('should respect max scan length limit', () => {
1213 const longContent = Array(1000).fill('const line = true;').join('\n');
1214 const document = new MockTextDocument('/test/test.ts', longContent);
1215 const editor = new MockTextEditor(document);
1216 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1217
1218 const generatorAny = generator as any;
1219 const comments = [' * ', ' * ', ' * '];
1220 const hasHeader = generatorAny.locateIfHeaderPresent(comments);
1221
1222 assert.strictEqual(hasHeader, false);
1223 });
1224 });
1225
1232 suite('Logo Integration', () => {
1237 test('should update logo randomizer instance', () => {
1238 generator = new CommentGenerator();
1239 const newRandomLogo = new RandomLogo();
1240
1241 generator.updateLogoInstanceRandomiser(newRandomLogo);
1242
1243 // Test passes if no error is thrown
1244 assert.ok(true);
1245 });
1246 });
1247
1255 suite('File Writing Operations', () => {
1260 test('should write header to empty file', async () => {
1261 const document = new MockTextDocument('/test/test.ts');
1262 const editor = new MockTextEditor(document);
1263
1264 mockActiveTextEditor = editor as any;
1265 mockShowInputBoxResponse = 'Test description';
1266
1267 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1268
1269 const generatorAny = generator as any;
1270 const comments = ['/* ', ' * ', ' */'];
1271 const status = await generatorAny.writeHeaderToFile(editor, comments);
1272
1273 assert.strictEqual(mockEditOperations.length, 1);
1274 assert.strictEqual(mockEditOperations[0].isInsert, true);
1275 assert.strictEqual(mockEditOperations[0].position.line, 0);
1276 assert.strictEqual(mockEditOperations[0].position.character, 0);
1277 assert.ok(mockEditOperations[0].text.includes('Test description'));
1278 });
1279
1284 test('should update existing header timestamp', async () => {
1285 const headerContent = `/*
1286 * ═══════════════════════ ◄ BEGIN TestProject ► ═══════════════════════
1287 * Logo:
1288 * ▄▄▄▄▄▄▄▄
1289 * ───────
1290 * Project: TestProject
1291 * File: test.ts
1292 * Created: 03-10-2025
1293 * LAST Modified: 15:30:45 03-10-2025
1294 * Description:
1295 * Test file
1296 * ───────
1297 * Copyright: © 2025
1298 * Purpose: Testing
1299 * ═══════════════════════ ◄ END TestProject ► ═══════════════════════
1300 */`;
1301
1302 const document = new MockTextDocument('/test/test.ts', headerContent);
1303 const editor = new MockTextEditor(document);
1304 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1305
1306 const generatorAny = generator as any;
1307 const comments = ['/* ', ' * ', ' */'];
1308
1309 // Simulate finding header bounds
1310 generatorAny.headerInnerStart = 1;
1311 generatorAny.headerInnerEnd = 15;
1312
1313 await generatorAny.updateEditDate(editor, comments);
1314
1315 assert.strictEqual(mockEditOperations.length, 1);
1316 assert.strictEqual(mockEditOperations[0].isInsert, false);
1317 // The text should contain the Last Modified prefix and timestamp
1318 const editText = mockEditOperations[0].text;
1319 assert.ok(editText.includes('Last Modified') || editText.includes('*'), 'Should include Last Modified or comment prefix');
1320 assert.ok(editText.includes('2025') || editText.length > 0, 'Should include year or have content');
1321 });
1322 });
1323
1331 suite('Main API Methods', () => {
1336 test('should inject header when no active editor', async () => {
1337 mockActiveTextEditor = undefined;
1338
1339 generator = new CommentGenerator(lazyFileLoader);
1340
1341 // Should not throw, just log error
1342 await generator.injectHeader();
1343
1344 assert.ok(true); // Test passes if no error thrown
1345 });
1346
1351 test('should inject header to TypeScript file', async () => {
1352 const document = new MockTextDocument('/test/test.ts');
1353 const editor = new MockTextEditor(document);
1354
1355 mockActiveTextEditor = editor as any;
1356 mockShowInputBoxResponse = 'Test file description';
1357
1358 generator = new CommentGenerator(lazyFileLoader, undefined, mockRandomLogo);
1359
1360 await generator.injectHeader();
1361
1362 // Should have written header
1363 assert.ok(mockEditOperations.length > 0);
1364 assert.ok(mockEditOperations[0].text.includes('Test file description'));
1365 });
1366
1371 test('should refresh header when configured', async () => {
1372 const headerContent = `/*
1373 * ═══════════════════════ ◄ BEGIN TestProject ► ═══════════════════════
1374 * Project: TestProject
1375 * File: test.ts
1376 * Last Modified: 15:30:45 03-10-2025
1377 * ═══════════════════════ ◄ END TestProject ► ═══════════════════════
1378 */`;
1379
1380 const document = new MockTextDocument('/test/test.ts', headerContent);
1381 const editor = new MockTextEditor(document);
1382
1383 mockActiveTextEditor = editor as any;
1384
1385 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1386
1387 await generator.refreshHeader(document as any);
1388
1389 // Should have updated timestamp if refresh is enabled
1390 // (depends on configuration, so we just test it doesn't throw)
1391 assert.ok(true);
1392 });
1393
1398 test('should handle refresh with no document', async () => {
1399 generator = new CommentGenerator(lazyFileLoader);
1400
1401 await generator.refreshHeader(undefined);
1402
1403 assert.ok(true); // Should not throw
1404 });
1405
1410 test('should handle refresh with closed document', async () => {
1411 const document = new MockTextDocument('/test/test.ts', '', 'typescript', vscode.EndOfLine.LF, true);
1412 const editor = new MockTextEditor(document);
1413
1414 mockActiveTextEditor = editor as any;
1415
1416 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1417
1418 await generator.refreshHeader(document as any);
1419
1420 assert.ok(true); // Should not throw
1421 });
1422 });
1423
1424 suite('Error Handling and Edge Cases', () => {
1429 test('should handle missing language comment loader', async () => {
1430 const document = new MockTextDocument('/test/test.ts');
1431 const editor = new MockTextEditor(document);
1432
1433 generator = new CommentGenerator(undefined, editor as any, mockRandomLogo);
1434
1435 const generatorAny = generator as any;
1436 const commentStyle = await generatorAny.determineCorrectComment();
1437
1438 assert.deepStrictEqual(commentStyle.singleLine, []);
1439 assert.deepStrictEqual(commentStyle.multiLine, []);
1440 });
1441
1446 test('should handle corrupted language configuration', async () => {
1447 // Create corrupted config file
1448 const corruptedConfigFile = path.join(tempDir, 'corrupted.json');
1449 await fs.writeFile(corruptedConfigFile, '{"invalid": structure}');
1450
1451 const corruptedLoader = new LazyFileLoader(corruptedConfigFile, tempDir);
1452 const document = new MockTextDocument('/test/test.ts');
1453 const editor = new MockTextEditor(document);
1454
1455 generator = new CommentGenerator(corruptedLoader, editor as any, mockRandomLogo);
1456
1457 const generatorAny = generator as any;
1458 const commentStyle = await generatorAny.determineCorrectComment();
1459
1460 assert.deepStrictEqual(commentStyle.singleLine, []);
1461 assert.deepStrictEqual(commentStyle.multiLine, []);
1462 });
1463
1468 test('should handle update without header bounds', async () => {
1469 const document = new MockTextDocument('/test/test.ts');
1470 const editor = new MockTextEditor(document);
1471
1472 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1473
1474 const generatorAny = generator as any;
1475 const comments = [' * ', ' * ', ' * '];
1476
1477 // Don't set header bounds
1478 await generatorAny.updateEditDate(editor, comments);
1479
1480 // Should not throw, should log error
1481 assert.ok(true);
1482 });
1483
1488 test('should handle undefined document in update', async () => {
1489 const document = new MockTextDocument('/test/test.ts');
1490 const editor = new MockTextEditor(document);
1491
1492 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1493
1494 const generatorAny = generator as any;
1495 const comments = [' * ', ' * ', ' * '];
1496
1497 // Clear document
1498 generatorAny.documentBody = undefined;
1499
1500 await generatorAny.updateEditDate(editor, comments);
1501
1502 assert.ok(true); // Should not throw
1503 });
1504
1509 test('should handle very long file paths', () => {
1510 const longPath = '/very/' + 'long/'.repeat(100) + 'path/to/file.ts';
1511 const document = new MockTextDocument(longPath);
1512 const editor = new MockTextEditor(document);
1513
1514 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1515
1516 const generatorAny = generator as any;
1517 assert.strictEqual(generatorAny.fileName, 'file.ts');
1518 assert.strictEqual(generatorAny.fileExtension, 'ts');
1519 });
1520
1525 test('should handle empty file names', () => {
1526 const document = new MockTextDocument('/test/');
1527 const editor = new MockTextEditor(document);
1528
1529 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1530
1531 const generatorAny = generator as any;
1532 assert.strictEqual(generatorAny.fileName, 'unknown');
1533 });
1534
1539 test('should handle special characters in file names', () => {
1540 const specialPath = '/test/file with spaces & symbols!.ts';
1541 const document = new MockTextDocument(specialPath);
1542 const editor = new MockTextEditor(document);
1543
1544 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1545
1546 const generatorAny = generator as any;
1547 assert.strictEqual(generatorAny.fileName, 'file with spaces & symbols!.ts');
1548 assert.strictEqual(generatorAny.fileExtension, 'ts');
1549 });
1550 });
1551
1559 suite('Integration Tests', () => {
1564 test('should complete full header injection workflow', async () => {
1565 const document = new MockTextDocument('/test/main.ts', '', 'typescript');
1566 const editor = new MockTextEditor(document);
1567
1568 mockActiveTextEditor = editor as any;
1569 mockShowInputBoxResponse = 'Main application entry point';
1570
1571 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1572
1573 await generator.injectHeader();
1574
1575 // Verify header was written
1576 assert.strictEqual(mockEditOperations.length, 1);
1577 assert.strictEqual(mockEditOperations[0].isInsert, true);
1578
1579 const headerText = mockEditOperations[0].text;
1580 assert.ok(headerText.includes('/*'));
1581 assert.ok(headerText.includes('Main application entry point'));
1582 assert.ok(headerText.includes('main.ts'));
1583 assert.ok(headerText.includes('2025'));
1584 assert.ok(headerText.includes('*/'));
1585 });
1586
1591 test('should handle complete refresh workflow with existing header', async () => {
1592 const existingHeader = `/*
1593 * ═══════════════════════ ◄ BEGIN AsperHeader ► ═══════════════════════
1594 * Project: AsperHeader
1595 * File: main.ts
1596 * Created: 01-10-2025
1597 * Last Modified: 14:25:30 01-10-2025
1598 * ═══════════════════════ ◄ END AsperHeader ► ═══════════════════════
1599 */
1600
1601const app = 'Hello World';`;
1602
1603 const document = new MockTextDocument('/test/main.ts', existingHeader, 'typescript');
1604 const editor = new MockTextEditor(document);
1605
1606 mockActiveTextEditor = editor as any;
1607
1608 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1609
1610 await generator.refreshHeader(document as any);
1611
1612 // Should have updated the timestamp if refresh is enabled
1613 assert.ok(true); // Test passes if no errors
1614 });
1615
1620 test('should handle multiple rapid operations', async () => {
1621 const document = new MockTextDocument('/test/rapid.ts');
1622 const editor = new MockTextEditor(document);
1623
1624 mockActiveTextEditor = editor as any;
1625 mockShowInputBoxResponse = 'Rapid test';
1626
1627 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1628
1629 // Simulate rapid calls
1630 const promises = [
1631 generator.injectHeader(),
1632 generator.refreshHeader(document as any),
1633 generator.injectHeader()
1634 ];
1635
1636 await Promise.all(promises);
1637
1638 // Should handle concurrent operations gracefully
1639 assert.ok(true);
1640 });
1641 });
1642});
Intelligent file header generation and management system.
Generic lazy file loader with caching and type safety @template T The expected type of the loaded fil...
Mock implementation of VS Code TextDocument for testing purposes.
constructor(filePath:string, content:string='', languageId:string='typescript', eol:vscode.EndOfLine=vscode.EndOfLine.LF, isClosed:boolean=false)
Creates a new mock text document with specified configuration.
Mock implementation of VS Code TextEditor for testing text editing operations.
constructor(document:MockTextDocument)
Creates a new mock text editor for the specified document.
import *as vscode from vscode
let originalShowQuickPick
let originalActiveTextEditor
let mockShowInputBoxResponse
import *as path from path
let mockEditOperations
let originalShowInputBox
let mockShowQuickPickResponse
import *as assert from assert
let mockActiveTextEditor
import *as fs from fs promises
import fs from fs
Definition esbuild.js:9
import *as vscode from vscode
Definition extension.ts:45
Represents a single line within a mock VS Code document for testing.
export const Record< string,(...args:any[])=> string
export const CodeConfig
Exported configuration singleton for extension-wide access @export Primary configuration interface us...