Asper Header  1.0.22
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 }> = [];
28let mockWorkspaceEdits: vscode.WorkspaceEdit[] = [];
29
38interface MockDocumentLine {
40 text: string;
42 range: vscode.Range;
44 lineNumber: number;
46 rangeIncludingLineBreak: vscode.Range;
48 firstNonWhitespaceCharacterIndex: number;
50 isEmptyOrWhitespace: boolean;
51}
52
70class MockTextDocument implements Partial<vscode.TextDocument> {
72 public lines: MockDocumentLine[] = [];
74 public uri: vscode.Uri;
76 public languageId: string;
78 public eol: vscode.EndOfLine;
80 public version: number;
82 public isClosed: boolean;
84 public lineCount: number = 0;
86 public fileName: string = '';
88 public isUntitled: boolean = false;
90 public encoding: string = 'utf8';
92 public isDirty: boolean = false;
93
107 filePath: string,
108 content: string = '',
109 languageId: string = 'typescript',
110 eol: vscode.EndOfLine = vscode.EndOfLine.LF,
111 isClosed: boolean = false
112 ) {
113 this.uri = vscode.Uri.file(filePath);
114 this.languageId = languageId;
115 this.eol = eol;
116 this.version = 1;
117 this.isClosed = isClosed;
118
119 this.setContent(content);
120 }
121
132 setContent(content: string): void {
133 const lines = content.split('\n');
134 this.lines = lines.map((text, index) => ({
135 text,
136 range: new vscode.Range(index, 0, index, text.length),
137 lineNumber: index,
138 rangeIncludingLineBreak: new vscode.Range(index, 0, index + 1, 0),
139 firstNonWhitespaceCharacterIndex: text.search(/\S/),
140 isEmptyOrWhitespace: text.trim().length === 0
141 }));
142 this.lineCount = this.lines.length;
143 this.fileName = path.basename(this.uri.fsPath);
144 }
145
152 lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine {
153 const line = typeof lineOrPosition === 'number' ? lineOrPosition : lineOrPosition.line;
154 if (line < 0 || line >= this.lines.length) {
155 throw new Error(`Line ${line} is out of range`);
156 }
157 return this.lines[line] as vscode.TextLine;
158 }
159
165 offsetAt(position: vscode.Position): number {
166 let offset = 0;
167 for (let i = 0; i < position.line && i < this.lines.length; i++) {
168 offset += this.lines[i].text.length + 1; // +1 for newline
169 }
170 return offset + Math.min(position.character, this.lines[position.line]?.text.length || 0);
171 }
172
178 positionAt(offset: number): vscode.Position {
179 let currentOffset = 0;
180 for (let line = 0; line < this.lines.length; line++) {
181 const lineLength = this.lines[line].text.length + 1; // +1 for newline
182 if (currentOffset + lineLength > offset) {
183 return new vscode.Position(line, offset - currentOffset);
184 }
185 currentOffset += lineLength;
186 }
187 return new vscode.Position(this.lines.length - 1, (this.lines[this.lines.length - 1]?.text.length || 0));
188 }
189
195 getText(range?: vscode.Range): string {
196 if (!range) {
197 return this.lines.map(line => line.text).join('\n');
198 }
199
200 if (range.start.line === range.end.line) {
201 const line = this.lines[range.start.line];
202 return line ? line.text.substring(range.start.character, range.end.character) : '';
203 }
204
205 let result = '';
206 for (let i = range.start.line; i <= range.end.line && i < this.lines.length; i++) {
207 const line = this.lines[i];
208 if (!line) { continue; }
209
210 if (i === range.start.line) {
211 result += line.text.substring(range.start.character);
212 } else if (i === range.end.line) {
213 result += line.text.substring(0, range.end.character);
214 } else {
215 result += line.text;
216 }
217
218 if (i < range.end.line) {
219 result += '\n';
220 }
221 }
222 return result;
223 }
224
230 validateRange(range: vscode.Range): vscode.Range {
231 return range;
232 }
233
239 validatePosition(position: vscode.Position): vscode.Position {
240 return position;
241 }
242
249 getWordRangeAtPosition(position: vscode.Position, regex?: RegExp): vscode.Range | undefined {
250 const line = this.lines[position.line];
251 if (!line) { return undefined; }
252
253 const wordRegex = regex || /[\w]+/g;
254 let match;
255 while ((match = wordRegex.exec(line.text)) !== null) {
256 if (match.index <= position.character && match.index + match[0].length >= position.character) {
257 return new vscode.Range(position.line, match.index, position.line, match.index + match[0].length);
258 }
259 }
260 return undefined;
261 }
262
267 save(): Thenable<boolean> {
268 return Promise.resolve(true);
269 }
270}
271
285class MockTextEditor implements Partial<vscode.TextEditor> {
287 public document: vscode.TextDocument;
288
297 this.document = document as unknown as vscode.TextDocument;
298 }
299
300 async edit(callback: (editBuilder: vscode.TextEditorEdit) => void): Promise<boolean> {
301 const mockEditBuilder = {
302 insert: (location: vscode.Position, value: string) => {
303 mockEditOperations.push({
304 position: location,
305 text: value,
306 isInsert: true
307 });
308 },
309 replace: (location: vscode.Range, value: string) => {
310 mockEditOperations.push({
311 position: location.start,
312 text: value,
313 isInsert: false,
314 range: location
315 });
316 }
317 };
318
319 callback(mockEditBuilder as vscode.TextEditorEdit);
320 return true;
321 }
322}
323
324// Store original VS Code methods
328
335suite('CommentGenerator Test Suite', function () {
337 let tempDir: string;
339 let languageConfigFile: string;
341 let lazyFileLoader: LazyFileLoader;
343 let mockRandomLogo: RandomLogo;
345 let generator: CommentGenerator;
346
355 setup(async () => {
356 // Create temporary directory for test files
357 tempDir = await fs.mkdtemp(path.join(require('os').tmpdir(), 'commentgen-test-'));
358
359 // Create language configuration file
360 languageConfigFile = path.join(tempDir, 'languages.json');
361 const languageConfig = {
362 langs: [
363 {
364 langs: ['typescript', 'javascript'],
365 fileExtensions: {
366 typescript: ['.ts', '.tsx'],
367 javascript: ['.js', '.jsx']
368 },
369 singleLine: ['//'],
370 multiLine: ['/*', ' *', ' */'],
371 prompt_comment_opening_type: false
372 },
373 {
374 langs: ['python'],
375 fileExtensions: {
376 python: ['.py']
377 },
378 singleLine: ['#'],
379 multiLine: [],
380 prompt_comment_opening_type: false
381 },
382 {
383 langs: ['c', 'cpp'],
384 fileExtensions: {
385 c: ['.c', '.h'],
386 cpp: ['.cpp', '.hpp', '.cxx']
387 },
388 singleLine: ['//'],
389 multiLine: ['/*', ' *', ' */'],
390 prompt_comment_opening_type: true
391 }
392 ]
393 };
394 await fs.writeFile(languageConfigFile, JSON.stringify(languageConfig, null, 2));
395
396 // Create lazy file loader
397 lazyFileLoader = new LazyFileLoader(languageConfigFile, tempDir);
398
399 // Create mock random logo
400 mockRandomLogo = new RandomLogo();
401
402 // Reset mock state
403 mockActiveTextEditor = undefined;
404 mockShowInputBoxResponse = undefined;
405 mockShowQuickPickResponse = undefined;
408
409 // Store original VS Code methods
410 originalActiveTextEditor = vscode.window.activeTextEditor;
411 originalShowInputBox = vscode.window.showInputBox;
412 originalShowQuickPick = vscode.window.showQuickPick;
413
414 // Mock VS Code APIs
415 Object.defineProperty(vscode.window, 'activeTextEditor', {
416 get: () => mockActiveTextEditor,
417 configurable: true
418 });
419
420 (vscode.window as any).showInputBox = async (options?: vscode.InputBoxOptions) => {
422 };
423
424 (vscode.window as any).showQuickPick = async (items: string[], options?: vscode.QuickPickOptions) => {
426 };
427
428 // Mock workspace.applyEdit
429 (vscode.workspace as any).applyEdit = async (edit: vscode.WorkspaceEdit) => {
430 mockWorkspaceEdits.push(edit);
431 return true;
432 };
433
434 // Reset CodeConfig to default values to ensure clean state
435 // This prevents configuration changes from other test suites from affecting these tests
436 await CodeConfig.refreshVariables();
437 });
438
446 teardown(async () => {
447 // Cleanup temporary directory
448 try {
449 await fs.rm(tempDir, { recursive: true, force: true });
450 } catch (error) {
451 // Ignore cleanup errors
452 }
453
454 // Restore original VS Code methods
455 Object.defineProperty(vscode.window, 'activeTextEditor', {
456 value: originalActiveTextEditor,
457 configurable: true
458 });
459 (vscode.window as any).showInputBox = originalShowInputBox;
460 (vscode.window as any).showQuickPick = originalShowQuickPick;
461 });
462
469 suite('Constructor and Initialization', () => {
474 test('should create instance with all parameters provided', () => {
475 const document = new MockTextDocument('/test/file.ts');
476 const editor = new MockTextEditor(document);
477
478 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
479
480 assert.ok(generator, 'Generator should be created successfully');
481 });
482
487 test('should create instance with minimal parameters', () => {
488 generator = new CommentGenerator();
489
490 assert.ok(generator, 'Generator should be created with undefined parameters');
491 });
492
497 test('should create instance with only language loader', () => {
498 generator = new CommentGenerator(lazyFileLoader);
499
500 assert.ok(generator, 'Generator should be created with language loader only');
501 });
502
507 test('should handle undefined editor gracefully', () => {
508 generator = new CommentGenerator(lazyFileLoader, undefined, mockRandomLogo);
509
510 assert.ok(generator, 'Generator should handle undefined editor');
511 });
512 });
513
521 suite('File Information Processing', () => {
526 test('should extract correct file metadata from TypeScript editor', () => {
527 const filePath = '/home/user/project/test.ts';
528 const document = new MockTextDocument(filePath, '', 'typescript');
529 const editor = new MockTextEditor(document);
530
531 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
532
533 // Access private properties through type assertion
534 const generatorAny = generator as any;
535 assert.strictEqual(generatorAny.fileName, 'test.ts');
536 assert.strictEqual(generatorAny.fileExtension, 'ts');
537 assert.strictEqual(generatorAny.languageId, 'typescript');
538 assert.strictEqual(generatorAny.filePath, filePath);
539 });
540
545 test('should extract correct file metadata from Python editor', () => {
546 const filePath = '/home/user/project/script.py';
547 const document = new MockTextDocument(filePath, '', 'python');
548 const editor = new MockTextEditor(document);
549
550 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
551
552 const generatorAny = generator as any;
553 assert.strictEqual(generatorAny.fileName, 'script.py');
554 assert.strictEqual(generatorAny.fileExtension, 'py');
555 assert.strictEqual(generatorAny.languageId, 'python');
556 });
557
562 test('should handle files without extensions', () => {
563 const filePath = '/home/user/Makefile';
564 const document = new MockTextDocument(filePath, '', 'makefile');
565 const editor = new MockTextEditor(document);
566
567 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
568
569 const generatorAny = generator as any;
570 assert.strictEqual(generatorAny.fileName, 'Makefile');
571 assert.strictEqual(generatorAny.fileExtension, 'none');
572 });
573
578 test('should handle different EOL types', () => {
579 const document = new MockTextDocument('/test/file.ts', '', 'typescript', vscode.EndOfLine.CRLF);
580 const editor = new MockTextEditor(document);
581
582 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
583
584 const generatorAny = generator as any;
585 assert.strictEqual(generatorAny.documentEOL, vscode.EndOfLine.CRLF);
586 });
587 });
588
596 suite('Comment Style Detection', () => {
601 test('should detect TypeScript comment style correctly', async () => {
602 const document = new MockTextDocument('/test/file.ts', '', 'typescript');
603 const editor = new MockTextEditor(document);
604
605 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
606
607 const generatorAny = generator as any;
608 const commentStyle = await generatorAny.determineCorrectComment();
609
610 assert.deepStrictEqual(commentStyle.singleLine, ['//']);
611 assert.deepStrictEqual(commentStyle.multiLine, ['/*', ' *', ' */']);
612 assert.strictEqual(commentStyle.prompt_comment_opening_type, false);
613 });
614
619 test('should detect Python comment style correctly', async () => {
620 const document = new MockTextDocument('/test/script.py', '', 'python');
621 const editor = new MockTextEditor(document);
622
623 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
624
625 const generatorAny = generator as any;
626 const commentStyle = await generatorAny.determineCorrectComment();
627
628 assert.deepStrictEqual(commentStyle.singleLine, ['#']);
629 assert.deepStrictEqual(commentStyle.multiLine, []);
630 });
631
636 test('should detect C++ comment style with prompting', async () => {
637 const document = new MockTextDocument('/test/main.cpp', '', 'cpp');
638 const editor = new MockTextEditor(document);
639
640 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
641
642 const generatorAny = generator as any;
643 const commentStyle = await generatorAny.determineCorrectComment();
644
645 assert.deepStrictEqual(commentStyle.singleLine, ['//']);
646 assert.deepStrictEqual(commentStyle.multiLine, ['/*', ' *', ' */']);
647 assert.strictEqual(commentStyle.prompt_comment_opening_type, true);
648 });
649
654 test('should fallback to file extension matching', async () => {
655 const document = new MockTextDocument('/test/file.tsx', '', 'unknown');
656 const editor = new MockTextEditor(document);
657
658 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
659
660 const generatorAny = generator as any;
661 const commentStyle = await generatorAny.determineCorrectComment();
662
663 // Should match TypeScript config based on .tsx extension
664 assert.deepStrictEqual(commentStyle.singleLine, ['//']);
665 assert.deepStrictEqual(commentStyle.multiLine, ['/*', ' *', ' */']);
666 });
667
672 test('should return empty style for unknown language', async () => {
673 const document = new MockTextDocument('/test/file.xyz', '', 'unknown');
674 const editor = new MockTextEditor(document);
675
676 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
677
678 const generatorAny = generator as any;
679 const commentStyle = await generatorAny.determineCorrectComment();
680
681 assert.deepStrictEqual(commentStyle.singleLine, []);
682 assert.deepStrictEqual(commentStyle.multiLine, []);
683 assert.strictEqual(commentStyle.prompt_comment_opening_type, false);
684 });
685 });
686
694 suite('User Input Handling', () => {
699 test('should get file description from user input', async () => {
700 const document = new MockTextDocument('/test/file.ts');
701 const editor = new MockTextEditor(document);
702 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
703
704 mockShowInputBoxResponse = 'This is a test file for the application';
705
706 const generatorAny = generator as any;
707 const description = await generatorAny.determineHeaderDescription();
708
709 assert.deepStrictEqual(description, ['This is a test file for the application']);
710 });
711
716 test('should handle empty description input', async () => {
717 const document = new MockTextDocument('/test/file.ts');
718 const editor = new MockTextEditor(document);
719 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
720
721 mockShowInputBoxResponse = undefined;
722
723 const generatorAny = generator as any;
724 const description = await generatorAny.determineHeaderDescription();
725
726 assert.deepStrictEqual(description, ['']);
727 });
728
733 test('should get file purpose from user input', async () => {
734 const document = new MockTextDocument('/test/file.ts');
735 const editor = new MockTextEditor(document);
736 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
737
738 mockShowInputBoxResponse = 'Main entry point for the application';
739
740 const generatorAny = generator as any;
741 const purpose = await generatorAny.determineHeaderPurpose();
742
743 assert.deepStrictEqual(purpose, ['Main entry point for the application']);
744 });
745
750 test('should get single comment option without prompting', async () => {
751 const document = new MockTextDocument('/test/file.ts');
752 const editor = new MockTextEditor(document);
753 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
754
755 const generatorAny = generator as any;
756 const result = await generatorAny.getSingleCommentOption(['//']);
757
758 assert.strictEqual(result, '//');
759 });
760
765 test('should prompt for comment selection when multiple options', async () => {
766 const document = new MockTextDocument('/test/file.ts');
767 const editor = new MockTextEditor(document);
768 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
769
771
772 const generatorAny = generator as any;
773 const result = await generatorAny.getSingleCommentOption(['//', '/*']);
774
775 assert.strictEqual(result, '/*');
776 });
777
782 test('should return first option when user cancels selection', async () => {
783 const document = new MockTextDocument('/test/file.ts');
784 const editor = new MockTextEditor(document);
785 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
786
787 mockShowQuickPickResponse = undefined;
788
789 const generatorAny = generator as any;
790 const result = await generatorAny.getSingleCommentOption(['//', '/*']);
791
792 assert.strictEqual(result, '//');
793 });
794
799 test('should throw error for empty comment options', async () => {
800 const document = new MockTextDocument('/test/file.ts');
801 const editor = new MockTextEditor(document);
802 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
803
804 const generatorAny = generator as any;
805
806 await assert.rejects(
807 async () => await generatorAny.getSingleCommentOption([]),
808 Error
809 );
810 });
811 });
812
820 suite('Header Content Generation', () => {
825 test('should generate correct header opener', () => {
826 const document = new MockTextDocument('/test/file.ts');
827 const editor = new MockTextEditor(document);
828 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
829
830 const generatorAny = generator as any;
831 const opener = generatorAny.headerOpener(' * ', vscode.EndOfLine.LF, 'TestProject');
832
833 assert.ok(opener.includes('TestProject'));
834 assert.ok(opener.includes(' * '));
835 assert.ok(opener.endsWith('\n'));
836 });
837
842 test('should generate correct header closer', () => {
843 const document = new MockTextDocument('/test/file.ts');
844 const editor = new MockTextEditor(document);
845 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
846
847 const generatorAny = generator as any;
848 const closer = generatorAny.headerCloser(' * ', vscode.EndOfLine.LF, 'TestProject');
849
850 assert.ok(closer.includes('TestProject'));
851 assert.ok(closer.includes(' * '));
852 assert.ok(closer.endsWith('\n'));
853 });
854
859 test('should generate creation date with correct format', () => {
860 const document = new MockTextDocument('/test/file.ts');
861 const editor = new MockTextEditor(document);
862 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
863
864 const generatorAny = generator as any;
865 const creationDate = generatorAny.addCreationDate(' * ', vscode.EndOfLine.LF);
866
867 assert.ok(creationDate.includes(' * '));
868 assert.ok(creationDate.includes(new Date().getFullYear().toString())); // Current year
869 assert.ok(creationDate.endsWith('\n'));
870 });
871
876 test('should generate last modified date with time', () => {
877 const document = new MockTextDocument('/test/file.ts');
878 const editor = new MockTextEditor(document);
879 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
880
881 const generatorAny = generator as any;
882 const modifiedDate = generatorAny.addLastModifiedDate(' * ', vscode.EndOfLine.LF);
883
884 assert.ok(modifiedDate.includes(' * '));
885 assert.ok(modifiedDate.includes(new Date().getFullYear().toString())); // Current year
886 assert.ok(modifiedDate.includes(':')); // Time separator
887 assert.ok(modifiedDate.endsWith('\n'));
888 });
889
894 test('should generate single line key-value pair', () => {
895 const document = new MockTextDocument('/test/file.ts');
896 const editor = new MockTextEditor(document);
897 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
898
899 const generatorAny = generator as any;
900 const singleLine = generatorAny.addSingleLineKey(' * ', vscode.EndOfLine.LF, 'Author', 'John Doe');
901
902 assert.ok(singleLine.includes(' * '));
903 assert.ok(singleLine.includes('Author'));
904 assert.ok(singleLine.includes('John Doe'));
905 assert.ok(singleLine.endsWith('\n'));
906 });
907
912 test('should generate multi-line key section', () => {
913 const document = new MockTextDocument('/test/file.ts');
914 const editor = new MockTextEditor(document);
915 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
916
917 const generatorAny = generator as any;
918 const multiLine = generatorAny.addMultilineKey(' * ', vscode.EndOfLine.LF, 'Description', ['Line 1', 'Line 2']);
919
920 assert.ok(multiLine.includes(' * '));
921 assert.ok(multiLine.includes('Description'));
922 assert.ok(multiLine.includes('Line 1'));
923 assert.ok(multiLine.includes('Line 2'));
924 });
925
930 test('should handle CRLF line endings correctly', () => {
931 const document = new MockTextDocument('/test/file.ts', '', 'typescript', vscode.EndOfLine.CRLF);
932 const editor = new MockTextEditor(document);
933 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
934
935 const generatorAny = generator as any;
936 const eolString = generatorAny.determineNewLine(vscode.EndOfLine.CRLF);
937
938 assert.strictEqual(eolString, '\r\n');
939 });
940
945 test('should handle LF line endings correctly', () => {
946 const document = new MockTextDocument('/test/file.ts');
947 const editor = new MockTextEditor(document);
948 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
949
950 const generatorAny = generator as any;
951 const eolString = generatorAny.determineNewLine(vscode.EndOfLine.LF);
952
953 assert.strictEqual(eolString, '\n');
954 });
955 });
956
964 suite('Comment Prefix Processing', () => {
969 test('should process multi-line comments with three parts', async () => {
970 const document = new MockTextDocument('/test/file.ts');
971 const editor = new MockTextEditor(document);
972 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
973
974 const commentStyle = {
975 singleLine: [],
976 multiLine: ['/*', ' *', ' */'],
977 prompt_comment_opening_type: false
978 };
979
980 const generatorAny = generator as any;
981 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
982
983 assert.strictEqual(prefixes.length, 3);
984 assert.ok(prefixes[0].includes('/*'));
985 assert.ok(prefixes[1].includes(' *'));
986 assert.ok(prefixes[2].includes(' */'));
987 });
988
993 test('should process multi-line comments with two parts', async () => {
994 const document = new MockTextDocument('/test/file.ts');
995 const editor = new MockTextEditor(document);
996 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
997
998 const commentStyle = {
999 singleLine: [],
1000 multiLine: ['<!--', '-->'],
1001 prompt_comment_opening_type: false
1002 };
1003
1004 const generatorAny = generator as any;
1005 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1006
1007 assert.strictEqual(prefixes.length, 3);
1008 assert.ok(prefixes[0].includes('<!--'));
1009 assert.strictEqual(prefixes[1].trim(), ''); // Empty middle
1010 assert.ok(prefixes[2].includes('-->'));
1011 });
1012
1017 test('should process single-line comments without prompting', async () => {
1018 const document = new MockTextDocument('/test/file.py');
1019 const editor = new MockTextEditor(document);
1020 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1021
1022 const commentStyle = {
1023 singleLine: ['#'],
1024 multiLine: [],
1025 prompt_comment_opening_type: false
1026 };
1027
1028 const generatorAny = generator as any;
1029 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1030
1031 assert.strictEqual(prefixes.length, 3);
1032 assert.ok(prefixes[0].includes('#'));
1033 assert.ok(prefixes[1].includes('#'));
1034 assert.ok(prefixes[2].includes('#'));
1035 });
1036
1041 test('should prompt for single-line comment selection', async () => {
1042 const document = new MockTextDocument('/test/file.cpp');
1043 const editor = new MockTextEditor(document);
1044 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1045
1047
1048 const commentStyle = {
1049 singleLine: ['//', '#'],
1050 multiLine: [],
1051 prompt_comment_opening_type: true
1052 };
1053
1054 const generatorAny = generator as any;
1055 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1056
1057 assert.strictEqual(prefixes.length, 3);
1058 assert.ok(prefixes[0].includes('//'));
1059 assert.ok(prefixes[1].includes('//'));
1060 assert.ok(prefixes[2].includes('//'));
1061 });
1062
1067 test('should handle empty comment configurations', async () => {
1068 const document = new MockTextDocument('/test/file.unknown');
1069 const editor = new MockTextEditor(document);
1070 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1071
1072 const commentStyle = {
1073 singleLine: [],
1074 multiLine: [],
1075 prompt_comment_opening_type: false
1076 };
1077
1078 const generatorAny = generator as any;
1079 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1080
1081 assert.strictEqual(prefixes.length, 3);
1082 // When no comment styles are available, should still have spacing
1083 assert.strictEqual(prefixes[0], ' '); // Just spacing
1084 assert.strictEqual(prefixes[1], ' ');
1085 assert.ok(prefixes[2].includes(' '));
1086 });
1087 });
1088
1096 suite('Header Detection and Parsing', () => {
1101 test('should detect existing header correctly', () => {
1102 const headerContent = `/*
1103 * +==== BEGIN AsperHeader =================+
1104 * Logo:
1105 * ▄▄▄▄▄▄▄▄
1106 * ───────
1107 * Project: AsperHeader
1108 * File: test.ts
1109 * Created: 03-10-2025
1110 * LAST Modified: 15:30:45 03-10-2025
1111 * Description:
1112 * Test file
1113 * ───────
1114 * Copyright: © 2025
1115 * Purpose: Testing
1116 * +==== END AsperHeader =================+
1117 */
1118
1119const someCode = true;`;
1120
1121 const document = new MockTextDocument('/test/test.ts', headerContent);
1122 const editor = new MockTextEditor(document);
1123 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1124
1125 const generatorAny = generator as any;
1126 const comments = [' * ', ' * ', ' * '];
1127 const hasHeader = generatorAny.locateIfHeaderPresent(comments);
1128
1129 assert.strictEqual(hasHeader, true);
1130 assert.ok(typeof generatorAny.headerInnerStart === 'number');
1131 assert.ok(typeof generatorAny.headerInnerEnd === 'number');
1132 });
1133
1138 test('should detect missing header correctly', () => {
1139 const content = `const someCode = true;
1140function myFunction() {
1141 return 'hello';
1142}`;
1143
1144 const document = new MockTextDocument('/test/test.ts', content);
1145 const editor = new MockTextEditor(document);
1146 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1147
1148 const generatorAny = generator as any;
1149 const comments = [' * ', ' * ', ' * '];
1150 const hasHeader = generatorAny.locateIfHeaderPresent(comments);
1151
1152 assert.strictEqual(hasHeader, false);
1153 assert.strictEqual(generatorAny.headerInnerStart, undefined);
1154 assert.strictEqual(generatorAny.headerInnerEnd, undefined);
1155 });
1156
1161 test('should detect broken header (opener without closer)', () => {
1162 const brokenContent = `/*
1163 * ═══════════════════════ ◄ BEGIN TestProject ► ═══════════════════════
1164 * Project: TestProject
1165 * File: test.ts
1166
1167const someCode = true;`;
1168
1169 const document = new MockTextDocument('/test/test.ts', brokenContent);
1170 const editor = new MockTextEditor(document);
1171 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1172
1173 const generatorAny = generator as any;
1174 const comments = [' * ', ' * ', ' * '];
1175 const hasHeader = generatorAny.locateIfHeaderPresent(comments);
1176
1177 assert.strictEqual(hasHeader, false);
1178 });
1179
1184 test('should detect broken header (closer without opener)', () => {
1185 const brokenContent = `const someCode = true;
1186 * ═══════════════════════ ◄ END TestProject ► ═══════════════════════
1187 */`;
1188
1189 const document = new MockTextDocument('/test/test.ts', brokenContent);
1190 const editor = new MockTextEditor(document);
1191 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1192
1193 const generatorAny = generator as any;
1194 const comments = [' * ', ' * ', ' * '];
1195 const hasHeader = generatorAny.locateIfHeaderPresent(comments);
1196
1197 assert.strictEqual(hasHeader, false);
1198 });
1199
1204 test('should handle closed document gracefully', () => {
1205 const document = new MockTextDocument('/test/test.ts', '', 'typescript', vscode.EndOfLine.LF, true);
1206 const editor = new MockTextEditor(document);
1207 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1208
1209 const generatorAny = generator as any;
1210 const comments = [' * ', ' * ', ' * '];
1211 const hasHeader = generatorAny.locateIfHeaderPresent(comments);
1212
1213 assert.strictEqual(hasHeader, undefined);
1214 });
1215
1220 test('should respect max scan length limit', () => {
1221 const longContent = Array(1000).fill('const line = true;').join('\n');
1222 const document = new MockTextDocument('/test/test.ts', longContent);
1223 const editor = new MockTextEditor(document);
1224 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1225
1226 const generatorAny = generator as any;
1227 const comments = [' * ', ' * ', ' * '];
1228 const hasHeader = generatorAny.locateIfHeaderPresent(comments);
1229
1230 assert.strictEqual(hasHeader, false);
1231 });
1232 });
1233
1240 suite('Logo Integration', () => {
1245 test('should update logo randomizer instance', () => {
1246 generator = new CommentGenerator();
1247 const newRandomLogo = new RandomLogo();
1248
1249 generator.updateLogoInstanceRandomiser(newRandomLogo);
1250
1251 // Test passes if no error is thrown
1252 assert.ok(true);
1253 });
1254 });
1255
1263 suite('File Writing Operations', () => {
1268 test('should write header to empty file', async () => {
1269 const document = new MockTextDocument('/test/test.ts');
1270 const editor = new MockTextEditor(document);
1271
1272 mockActiveTextEditor = editor as any;
1273 mockShowInputBoxResponse = 'Test description';
1274
1275 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1276
1277 const generatorAny = generator as any;
1278 const comments = ['/* ', ' * ', ' */'];
1279 const status = await generatorAny.writeHeaderToFile(document, comments);
1280
1281 assert.strictEqual(status, 0);
1282 assert.strictEqual(mockWorkspaceEdits.length, 1);
1283 });
1284
1289 test('should update existing header timestamp', async () => {
1290 // Use header that matches current constants (telegraph markers, LAST MODIFIED all caps)
1291 const headerContent = `/*
1292 * +==== BEGIN AsperHeader =================+
1293 * LOGO:
1294 * ...........+++....................
1295 * ..........+++++...................
1296 * PROJECT: AsperHeader
1297 * FILE: test.ts
1298 * CREATION DATE: 03-10-2025
1299 * LAST MODIFIED: 15:30:45 03-10-2025
1300 * DESCRIPTION:
1301 * Test file
1302 * /STOP
1303 * COPYRIGHT: (c) Asperguide
1304 * PURPOSE: Testing
1305 * // AR
1306 * +==== END AsperHeader =================+
1307 */`;
1308
1309 const document = new MockTextDocument('/test/test.ts', headerContent);
1310 const editor = new MockTextEditor(document);
1311 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1312
1313 const generatorAny = generator as any;
1314 const comments = ['/* ', ' * ', ' */'];
1315
1316 // Simulate finding header bounds - headerInnerStart/End must bracket the LAST MODIFIED line
1317 generatorAny.headerInnerStart = 1;
1318 generatorAny.headerInnerEnd = 14;
1319
1320 await generatorAny.updateEditDate(document, comments);
1321
1322 assert.strictEqual(mockWorkspaceEdits.length, 1);
1323 });
1324
1325 test('should fail to update if LAST MODIFIED key has wrong casing (case-sensitive)', async () => {
1326 // Regression guard for d220005: key changed from "LAST Modified" to "LAST MODIFIED"
1327 // updateEditDate uses includes() with case-sensitive match, so wrong casing should NOT update
1328 const headerContent = `/*
1329 * +==== BEGIN AsperHeader =================+
1330 * LOGO:
1331 * ...........+++....................
1332 * PROJECT: AsperHeader
1333 * FILE: test.ts
1334 * CREATION DATE: 03-10-2025
1335 * LAST Modified: 15:30:45 03-10-2025
1336 * DESCRIPTION:
1337 * Test file
1338 * /STOP
1339 * COPYRIGHT: (c) Asperguide
1340 * PURPOSE: Testing
1341 * // AR
1342 * +==== END AsperHeader =================+
1343 */`;
1344
1345 const document = new MockTextDocument('/test/test.ts', headerContent);
1346 const editor = new MockTextEditor(document);
1347 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1348
1349 const generatorAny = generator as any;
1350 const comments = ['/* ', ' * ', ' */'];
1351
1352 generatorAny.headerInnerStart = 1;
1353 generatorAny.headerInnerEnd = 14;
1354
1355 await generatorAny.updateEditDate(document, comments);
1356
1357 // Should NOT have performed an edit because key casing is wrong
1358 assert.strictEqual(mockWorkspaceEdits.length, 0);
1359 });
1360 });
1361
1370 suite('Logo Height and MaxScanLength Calculation', () => {
1371 test('should compute maxScanLength as maxScanLength + default logo height (dynamic)', () => {
1372 const document = new MockTextDocument('/test/file.ts', '', 'typescript');
1373 const editor = new MockTextEditor(document);
1374 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1375 const generatorAny = generator as any;
1376 const base = CodeConfig.get("maxScanLength");
1377 const expected = base + CodeConfig.get("headerLogo").length;
1378 // base=100, default logo is v2 with 23 lines => 123; dynamic via Config.get("maxScanLength")
1379 assert.strictEqual(generatorAny.maxScanLength, expected, `maxScanLength should be maxScanLength (${base}) + logo height (${CodeConfig.get("headerLogo").length})`);
1380 assert.strictEqual(generatorAny.maxScanLength, 123);
1381 });
1382
1383 test('should compute maxScanLength using versioned logo when enabled (v1) - dynamic Config', () => {
1384 const document = new MockTextDocument('/test/file.ts', '', 'typescript');
1385 const editor = new MockTextEditor(document);
1386 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1387 const generatorAny = generator as any;
1388 // Stub Config.get to simulate user enabling versioned logo via settings (1.0.22 dynamic)
1389 const origGet = generatorAny.Config.get.bind(generatorAny.Config);
1390 generatorAny.Config.get = (key: string) => {
1391 if (key === "useHeaderLogoVersion") { return true; }
1392 if (key === "headerLogoVersionReference") { return "v1"; }
1393 return origGet(key);
1394 };
1395 generatorAny.updateFileInfo(document as any);
1396 const base = origGet("maxScanLength");
1397 const versions = origGet("headerLogoVersions");
1398 const expected = base + versions["v1"].length;
1399 assert.strictEqual(generatorAny.maxScanLength, expected);
1400 assert.strictEqual(versions["v1"].length, 17);
1401 assert.strictEqual(generatorAny.maxScanLength, 117);
1402 generatorAny.Config.get = origGet;
1403 });
1404
1405 test('should compute maxScanLength using versioned logo v1-wide (25 lines) - dynamic', () => {
1406 const document = new MockTextDocument('/test/file.ts', '', 'typescript');
1407 const editor = new MockTextEditor(document);
1408 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1409 const generatorAny = generator as any;
1410 const origGet = generatorAny.Config.get.bind(generatorAny.Config);
1411 generatorAny.Config.get = (key: string) => {
1412 if (key === "useHeaderLogoVersion") { return true; }
1413 if (key === "headerLogoVersionReference") { return "v1-wide"; }
1414 return origGet(key);
1415 };
1416 generatorAny.updateFileInfo(document as any);
1417 const base = origGet("maxScanLength");
1418 const versions = origGet("headerLogoVersions");
1419 const expected = base + versions["v1-wide"].length;
1420 assert.strictEqual(generatorAny.maxScanLength, expected);
1421 assert.strictEqual(versions["v1-wide"].length, 25);
1422 assert.strictEqual(generatorAny.maxScanLength, 125);
1423 generatorAny.Config.get = origGet;
1424 });
1425
1426 test('should compute maxScanLength using v2 when versioned (also 23 lines) - dynamic', () => {
1427 const document = new MockTextDocument('/test/file.ts', '', 'typescript');
1428 const editor = new MockTextEditor(document);
1429 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1430 const generatorAny = generator as any;
1431 const origGet = generatorAny.Config.get.bind(generatorAny.Config);
1432 generatorAny.Config.get = (key: string) => {
1433 if (key === "useHeaderLogoVersion") { return true; }
1434 if (key === "headerLogoVersionReference") { return "v2"; }
1435 return origGet(key);
1436 };
1437 generatorAny.updateFileInfo(document as any);
1438 const base = origGet("maxScanLength");
1439 const versions = origGet("headerLogoVersions");
1440 const expected = base + versions["v2"].length;
1441 assert.strictEqual(generatorAny.maxScanLength, expected);
1442 assert.strictEqual(generatorAny.maxScanLength, 123);
1443 generatorAny.Config.get = origGet;
1444 });
1445
1446 test('should keep maxScanLength at base when versioned logo reference is unknown - dynamic', () => {
1447 const document = new MockTextDocument('/test/file.ts', '', 'typescript');
1448 const editor = new MockTextEditor(document);
1449 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1450 const generatorAny = generator as any;
1451 const origGet = generatorAny.Config.get.bind(generatorAny.Config);
1452 generatorAny.Config.get = (key: string) => {
1453 if (key === "useHeaderLogoVersion") { return true; }
1454 if (key === "headerLogoVersionReference") { return "nonexistent"; }
1455 return origGet(key);
1456 };
1457 generatorAny.updateFileInfo(document as any);
1458 const base = origGet("maxScanLength");
1459 assert.strictEqual(generatorAny.maxScanLength, base);
1460 assert.strictEqual(generatorAny.maxScanLength, 100);
1461 generatorAny.Config.get = origGet;
1462 });
1463
1464 test('should reflect user-changed maxScanLength dynamically (e.g. 200)', () => {
1465 const document = new MockTextDocument('/test/file.ts', '', 'typescript');
1466 const editor = new MockTextEditor(document);
1467 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1468 const generatorAny = generator as any;
1469 const origGet = generatorAny.Config.get.bind(generatorAny.Config);
1470 generatorAny.Config.get = (key: string) => {
1471 if (key === "maxScanLength") { return 200; }
1472 if (key === "useHeaderLogoVersion") { return false; }
1473 return origGet(key);
1474 };
1475 generatorAny.updateFileInfo(document as any);
1476 // 200 + default logo 23 = 223
1477 assert.strictEqual(generatorAny.maxScanLength, 223);
1478 generatorAny.Config.get = origGet;
1479 });
1480
1481 test('should refresh cached logo fields dynamically on updateFileInfo', () => {
1482 const document = new MockTextDocument('/test/file.ts', '', 'typescript');
1483 const editor = new MockTextEditor(document);
1484 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1485 const generatorAny = generator as any;
1486 // Initially default state
1487 assert.strictEqual(generatorAny.useHeaderLogoVersion, false);
1488 // Simulate user toggling via settings then opening new document
1489 const origGet = generatorAny.Config.get.bind(generatorAny.Config);
1490 generatorAny.Config.get = (key: string) => {
1491 if (key === "useHeaderLogoVersion") { return true; }
1492 if (key === "headerLogoVersionReference") { return "v1"; }
1493 return origGet(key);
1494 };
1495 generatorAny.updateFileInfo(document as any);
1496 assert.strictEqual(generatorAny.useHeaderLogoVersion, true);
1497 assert.strictEqual(generatorAny.headerLogoVersionReference, "v1");
1498 generatorAny.Config.get = origGet;
1499 });
1500 });
1501
1508 suite('Golden Header Snapshot', () => {
1509 test('should build header with exact structure: opener, LOGO(23), PROJECT, FILE, DESCRIPTION+STOP, closer', async () => {
1510 const document = new MockTextDocument('/test/golden.ts', '', 'typescript');
1511 const editor = new MockTextEditor(document);
1512 mockShowInputBoxResponse = 'Golden description';
1513 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1514 const generatorAny = generator as any;
1515 // Force deterministic description/purpose via Config projectDescription
1516 const origGet = generatorAny.Config.get.bind(generatorAny.Config);
1517 generatorAny.Config.get = (key: string) => {
1518 if (key === 'projectDescription') { return 'Golden description'; }
1519 return origGet(key);
1520 };
1521 const commentStyle = { singleLine: ['//'], multiLine: ['/*', ' *', ' */'], prompt_comment_opening_type: false, language: 'typescript' };
1522 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1523 const headerLines = await generatorAny.buildTheHeader(prefixes, 'typescript');
1524 const headerText = headerLines.join('');
1525 const lines = headerText.split('\n').filter((l: string) => l.length > 0);
1526 // Opener+closer with telegraph markers
1527 assert.ok(lines.some((l: string) => l.includes('+==== BEGIN AsperHeader')), 'Missing header opener');
1528 assert.ok(lines.some((l: string) => l.includes('+==== END AsperHeader')), 'Missing header closer');
1529 // LOGO section: key + logo height (23 for v2) + /STOP
1530 const logoKeyIdx = lines.findIndex((l: string) => l.includes('LOGO:'));
1531 assert.notStrictEqual(logoKeyIdx, -1, 'Missing LOGO key');
1532 const logoHeight = CodeConfig.get('headerLogo').length;
1533 assert.strictEqual(logoHeight, 23, 'Default logo should be 23 lines');
1534 const stopAfterLogo = lines[logoKeyIdx + 1 + logoHeight];
1535 assert.ok(stopAfterLogo.includes('/STOP'), 'Expected /STOP after LOGO, got ' + stopAfterLogo);
1536 // DESCRIPTION section also ends with /STOP
1537 const descIdx = lines.findIndex((l: string) => l.includes('DESCRIPTION:'));
1538 assert.notStrictEqual(descIdx, -1, 'Missing DESCRIPTION key');
1539 assert.ok(lines[descIdx + 2].includes('/STOP'), 'DESCRIPTION should be followed by content + /STOP');
1540 // Must contain PROJECT, FILE, CREATION DATE, LAST MODIFIED, COPYRIGHT, PURPOSE, // AR
1541 ['PROJECT:', 'FILE:', 'CREATION DATE:', 'LAST MODIFIED:', 'COPYRIGHT:', 'PURPOSE:', '// AR'].forEach(token => {
1542 assert.ok(lines.some((l: string) => l.includes(token)), 'Missing token ' + token);
1543 });
1544 // Comment delimiters - avoid /* and */ inside template literals to not confuse Doxygen/TS
1545 assert.ok(lines[0].trim() === '/*', 'First line should be /*, got ' + lines[0]);
1546 assert.ok(lines[lines.length - 1].trim() === '*/', 'Last line should be */, got ' + lines[lines.length - 1]);
1547 generatorAny.Config.get = origGet;
1548 });
1549
1550 test('should build header with versioned logo v1 (17 lines) when dynamic flag toggled', async () => {
1551 const document = new MockTextDocument('/test/golden.ts', '', 'typescript');
1552 const editor = new MockTextEditor(document);
1553 mockShowInputBoxResponse = 'desc';
1554 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1555 const generatorAny = generator as any;
1556 const origGet = generatorAny.Config.get.bind(generatorAny.Config);
1557 generatorAny.Config.get = (key: string) => {
1558 if (key === 'projectDescription') { return 'desc'; }
1559 if (key === 'useHeaderLogoVersion') { return true; }
1560 if (key === 'headerLogoVersionReference') { return 'v1'; }
1561 return origGet(key);
1562 };
1563 const commentStyle = { singleLine: ['//'], multiLine: ['/*', ' *', ' */'], prompt_comment_opening_type: false, language: 'typescript' };
1564 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1565 const headerLines = await generatorAny.buildTheHeader(prefixes, 'typescript');
1566 const headerText = headerLines.join('');
1567 const lines = headerText.split('\n').filter((l: string) => l.length > 0);
1568 const logoKeyIdx = lines.findIndex((l: string) => l.includes('LOGO:'));
1569 const v1Height = origGet('headerLogoVersions')['v1'].length;
1570 assert.strictEqual(v1Height, 17);
1571 const stopAfterLogo = lines[logoKeyIdx + 1 + v1Height];
1572 assert.ok(stopAfterLogo.includes('/STOP'), 'v1 LOGO should be 17 lines then /STOP');
1573 generatorAny.Config.get = origGet;
1574 });
1575 });
1576
1584 suite('Main API Methods', () => {
1589 test('should inject header when no active editor', async () => {
1590 mockActiveTextEditor = undefined;
1591
1592 generator = new CommentGenerator(lazyFileLoader);
1593
1594 // Should not throw, just log error
1595 await generator.injectHeader();
1596
1597 assert.ok(true); // Test passes if no error thrown
1598 });
1599
1604 test('should inject header to TypeScript file', async () => {
1605 const document = new MockTextDocument('/test/test.ts');
1606 const editor = new MockTextEditor(document);
1607
1608 mockActiveTextEditor = editor as any;
1609 mockShowInputBoxResponse = 'Test file description';
1610
1611 generator = new CommentGenerator(lazyFileLoader, undefined, mockRandomLogo);
1612
1613 await generator.injectHeader();
1614
1615 // Should have written header
1616 assert.ok(mockWorkspaceEdits.length > 0);
1617 });
1618
1623 test('should refresh header when configured', async () => {
1624 const headerContent = `/*
1625 * ═══════════════════════ ◄ BEGIN TestProject ► ═══════════════════════
1626 * Project: TestProject
1627 * File: test.ts
1628 * Last Modified: 15:30:45 03-10-2025
1629 * ═══════════════════════ ◄ END TestProject ► ═══════════════════════
1630 */`;
1631
1632 const document = new MockTextDocument('/test/test.ts', headerContent);
1633 const editor = new MockTextEditor(document);
1634
1635 mockActiveTextEditor = editor as any;
1636
1637 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1638
1639 await generator.refreshHeader(document as any);
1640
1641 // Should have updated timestamp if refresh is enabled
1642 // (depends on configuration, so we just test it doesn't throw)
1643 assert.ok(true);
1644 });
1645
1650 test('should handle refresh with no document', async () => {
1651 generator = new CommentGenerator(lazyFileLoader);
1652
1653 await generator.refreshHeader(undefined);
1654
1655 assert.ok(true); // Should not throw
1656 });
1657
1662 test('should handle refresh with closed document', async () => {
1663 const document = new MockTextDocument('/test/test.ts', '', 'typescript', vscode.EndOfLine.LF, true);
1664 const editor = new MockTextEditor(document);
1665
1666 mockActiveTextEditor = editor as any;
1667
1668 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1669
1670 await generator.refreshHeader(document as any);
1671
1672 assert.ok(true); // Should not throw
1673 });
1674 });
1675
1683 suite('Language Customization Features', () => {
1688 test('should trim trailing spaces when enabled', () => {
1689 const document = new MockTextDocument('/test/test.ts');
1690 const editor = new MockTextEditor(document);
1691 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1692
1693 const generatorAny = generator as any;
1694 generatorAny.trimTrailingSpaces = true;
1695
1696 const result = generatorAny.mySmartTrimmer('content with spaces ');
1697 assert.strictEqual(result, 'content with spaces');
1698 });
1699
1704 test('should preserve trailing spaces when disabled', () => {
1705 const document = new MockTextDocument('/test/test.ts');
1706 const editor = new MockTextEditor(document);
1707 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1708
1709 const generatorAny = generator as any;
1710 generatorAny.trimTrailingSpaces = false;
1711
1712 const result = generatorAny.mySmartTrimmer('content with spaces ');
1713 assert.strictEqual(result, 'content with spaces ');
1714 });
1715
1720 test('should prepend language-specific text when configured', () => {
1721 const document = new MockTextDocument('/test/test.py', '', 'python');
1722 const editor = new MockTextEditor(document);
1723 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1724
1725 const generatorAny = generator as any;
1726 generatorAny.languagePrepend = { python: '#!/usr/bin/env python\n' };
1727
1728 let buildHeader: string[] = [];
1729 buildHeader = generatorAny.prependIfPresent(buildHeader, vscode.EndOfLine.LF, 'python');
1730
1731 assert.strictEqual(buildHeader.length, 1);
1732 assert.strictEqual(buildHeader[0], '#!/usr/bin/env python\n');
1733 });
1734
1739 test('should handle array prepend content', () => {
1740 const document = new MockTextDocument('/test/test.py', '', 'python');
1741 const editor = new MockTextEditor(document);
1742 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1743
1744 const generatorAny = generator as any;
1745 generatorAny.languagePrepend = { python: ['#!/usr/bin/env python', '# -*- coding: utf-8 -*-'] };
1746
1747 let buildHeader: string[] = [];
1748 buildHeader = generatorAny.prependIfPresent(buildHeader, vscode.EndOfLine.LF, 'python');
1749
1750 assert.strictEqual(buildHeader.length, 1);
1751 assert.ok(buildHeader[0].includes('#!/usr/bin/env python'));
1752 });
1753
1758 test('should skip prepend when language is undefined', () => {
1759 const document = new MockTextDocument('/test/test.ts');
1760 const editor = new MockTextEditor(document);
1761 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1762
1763 const generatorAny = generator as any;
1764 generatorAny.languagePrepend = { python: '#!/usr/bin/env python\n' };
1765
1766 let buildHeader: string[] = [];
1767 buildHeader = generatorAny.prependIfPresent(buildHeader, vscode.EndOfLine.LF, undefined);
1768
1769 assert.strictEqual(buildHeader.length, 0);
1770 });
1771
1776 test('should append language-specific text when configured', () => {
1777 const document = new MockTextDocument('/test/test.py', '', 'python');
1778 const editor = new MockTextEditor(document);
1779 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1780
1781 const generatorAny = generator as any;
1782 generatorAny.languageAppend = { python: '\n# Code begins\n' };
1783
1784 let buildHeader: string[] = [];
1785 buildHeader = generatorAny.appendIfPresent(buildHeader, vscode.EndOfLine.LF, 'python');
1786
1787 assert.strictEqual(buildHeader.length, 1);
1788 assert.strictEqual(buildHeader[0], '\n# Code begins\n');
1789 });
1790
1795 test('should handle array append content', () => {
1796 const document = new MockTextDocument('/test/test.py', '', 'python');
1797 const editor = new MockTextEditor(document);
1798 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1799
1800 const generatorAny = generator as any;
1801 generatorAny.languageAppend = { python: ['', '# Code begins', '# ============'] };
1802
1803 let buildHeader: string[] = [];
1804 buildHeader = generatorAny.appendIfPresent(buildHeader, vscode.EndOfLine.LF, 'python');
1805
1806 assert.strictEqual(buildHeader.length, 1);
1807 assert.ok(buildHeader[0].includes('# Code begins'));
1808 });
1809
1814 test('should skip append when language is undefined', () => {
1815 const document = new MockTextDocument('/test/test.ts');
1816 const editor = new MockTextEditor(document);
1817 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1818
1819 const generatorAny = generator as any;
1820 generatorAny.languageAppend = { python: '\n# Code begins\n' };
1821
1822 let buildHeader: string[] = [];
1823 buildHeader = generatorAny.appendIfPresent(buildHeader, vscode.EndOfLine.LF, undefined);
1824
1825 assert.strictEqual(buildHeader.length, 0);
1826 });
1827
1832 test('should apply single-line comment override when configured', () => {
1833 const document = new MockTextDocument('/test/test.idr', '', 'idris');
1834 const editor = new MockTextEditor(document);
1835 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1836
1837 const generatorAny = generator as any;
1838 generatorAny.singleLineOverride = { idris: '|||' };
1839
1840 const commentStyle = {
1841 singleLine: ['--'],
1842 multiLine: [],
1843 prompt_comment_opening_type: false,
1844 language: 'idris'
1845 };
1846
1847 const result = generatorAny.getOverrideIfPresent(commentStyle);
1848
1849 assert.deepStrictEqual(result.singleLine, ['|||']);
1850 assert.strictEqual(result.language, 'idris');
1851 });
1852
1857 test('should apply multi-line comment override when configured', () => {
1858 const document = new MockTextDocument('/test/test.c', '', 'c');
1859 const editor = new MockTextEditor(document);
1860 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1861
1862 const generatorAny = generator as any;
1863 generatorAny.multiLineOverride = { c: ['/*', '**', '*/'] };
1864
1865 const commentStyle = {
1866 singleLine: ['//'],
1867 multiLine: ['/*', ' *', ' */'],
1868 prompt_comment_opening_type: false,
1869 language: 'c'
1870 };
1871
1872 const result = generatorAny.getOverrideIfPresent(commentStyle);
1873
1874 assert.deepStrictEqual(result.multiLine, ['/*', '**', '*/']);
1875 assert.strictEqual(result.language, 'c');
1876 });
1877
1882 test('should handle array single-line comment override', () => {
1883 const document = new MockTextDocument('/test/test.idr', '', 'idris');
1884 const editor = new MockTextEditor(document);
1885 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1886
1887 const generatorAny = generator as any;
1888 generatorAny.singleLineOverride = { idris: ['|||', '---'] };
1889
1890 const commentStyle = {
1891 singleLine: ['--'],
1892 multiLine: [],
1893 prompt_comment_opening_type: false,
1894 language: 'idris'
1895 };
1896
1897 const result = generatorAny.getOverrideIfPresent(commentStyle);
1898
1899 assert.deepStrictEqual(result.singleLine, ['|||', '---']);
1900 });
1901
1906 test('should skip override when language is undefined', () => {
1907 const document = new MockTextDocument('/test/test.ts');
1908 const editor = new MockTextEditor(document);
1909 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1910
1911 const generatorAny = generator as any;
1912 generatorAny.singleLineOverride = { idris: '|||' };
1913
1914 const commentStyle = {
1915 singleLine: ['--'],
1916 multiLine: [],
1917 prompt_comment_opening_type: false,
1918 language: undefined
1919 };
1920
1921 const result = generatorAny.getOverrideIfPresent(commentStyle);
1922
1923 assert.deepStrictEqual(result.singleLine, ['--']);
1924 assert.strictEqual(result.language, undefined);
1925 });
1926
1931 test('should prefer single-line comments when configured', async () => {
1932 const document = new MockTextDocument('/test/test.ts', '', 'typescript');
1933 const editor = new MockTextEditor(document);
1934 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1935
1936 const generatorAny = generator as any;
1937 generatorAny.preferSingleLineComments = true;
1938
1939 const commentStyle = {
1940 singleLine: ['//'],
1941 multiLine: ['/*', ' *', ' */'],
1942 prompt_comment_opening_type: false,
1943 language: 'typescript'
1944 };
1945
1946 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1947
1948 // All three prefixes should be the same (single-line)
1949 assert.ok(prefixes[0].includes('//'));
1950 assert.ok(prefixes[1].includes('//'));
1951 assert.ok(prefixes[2].includes('//'));
1952 });
1953
1958 test('should use multi-line comments by default', async () => {
1959 const document = new MockTextDocument('/test/test.ts', '', 'typescript');
1960 const editor = new MockTextEditor(document);
1961 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1962
1963 const generatorAny = generator as any;
1964 generatorAny.preferSingleLineComments = false;
1965
1966 const commentStyle = {
1967 singleLine: ['//'],
1968 multiLine: ['/*', ' *', ' */'],
1969 prompt_comment_opening_type: false,
1970 language: 'typescript'
1971 };
1972
1973 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1974
1975 // Should use multi-line style
1976 assert.ok(prefixes[0].includes('/*'));
1977 assert.ok(prefixes[1].includes(' *'));
1978 assert.ok(prefixes[2].includes(' */'));
1979 });
1980 });
1981
1988 suite('Shebang Detection', () => {
1993 test('should detect bash shebang and skip first line', () => {
1994 const content = '#!/bin/bash\necho "Hello World"';
1995 const document = new MockTextDocument('/test/script.sh', content, 'shellscript');
1996 const editor = new MockTextEditor(document);
1997 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1998
1999 const generatorAny = generator as any;
2000 const insertLine = generatorAny.skipFirstLineInDocument(document);
2001
2002 assert.strictEqual(insertLine, 1);
2003 });
2004
2009 test('should detect python shebang and skip first line', () => {
2010 const content = '#!/usr/bin/env python3\nprint("Hello World")';
2011 const document = new MockTextDocument('/test/script.py', content, 'python');
2012 const editor = new MockTextEditor(document);
2013 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2014
2015 const generatorAny = generator as any;
2016 const insertLine = generatorAny.skipFirstLineInDocument(document);
2017
2018 assert.strictEqual(insertLine, 1);
2019 });
2020
2025 test('should insert at line 0 when no shebang present', () => {
2026 const content = 'import sys\nprint("Hello World")';
2027 const document = new MockTextDocument('/test/script.py', content, 'python');
2028 const editor = new MockTextEditor(document);
2029 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2030
2031 const generatorAny = generator as any;
2032 const insertLine = generatorAny.skipFirstLineInDocument(document);
2033
2034 assert.strictEqual(insertLine, 0);
2035 });
2036
2041 test('should handle empty document', () => {
2042 const document = new MockTextDocument('/test/empty.sh', '', 'shellscript');
2043 const editor = new MockTextEditor(document);
2044 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2045
2046 const generatorAny = generator as any;
2047 const insertLine = generatorAny.skipFirstLineInDocument(document);
2048
2049 assert.strictEqual(insertLine, 0);
2050 });
2051
2056 test('should not treat regular hash comments as shebang', () => {
2057 const content = '# This is a comment\nprint("Hello")';
2058 const document = new MockTextDocument('/test/script.py', content, 'python');
2059 const editor = new MockTextEditor(document);
2060 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2061
2062 const generatorAny = generator as any;
2063 const insertLine = generatorAny.skipFirstLineInDocument(document);
2064
2065 assert.strictEqual(insertLine, 0);
2066 });
2067
2072 test('should detect shebang with arguments', () => {
2073 const content = '#!/usr/bin/env node --harmony\nconsole.log("Test")';
2074 const document = new MockTextDocument('/test/script.js', content, 'javascript');
2075 const editor = new MockTextEditor(document);
2076 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2077
2078 const generatorAny = generator as any;
2079 const insertLine = generatorAny.skipFirstLineInDocument(document);
2080
2081 assert.strictEqual(insertLine, 1);
2082 });
2083 });
2084
2085 suite('Error Handling and Edge Cases', () => {
2090 test('should handle missing language comment loader', async () => {
2091 const document = new MockTextDocument('/test/test.ts');
2092 const editor = new MockTextEditor(document);
2093
2094 generator = new CommentGenerator(undefined, editor as any, mockRandomLogo);
2095
2096 const generatorAny = generator as any;
2097 const commentStyle = await generatorAny.determineCorrectComment();
2098
2099 assert.deepStrictEqual(commentStyle.singleLine, []);
2100 assert.deepStrictEqual(commentStyle.multiLine, []);
2101 });
2102
2107 test('should handle corrupted language configuration', async () => {
2108 // Create corrupted config file
2109 const corruptedConfigFile = path.join(tempDir, 'corrupted.json');
2110 await fs.writeFile(corruptedConfigFile, '{"invalid": structure}');
2111
2112 const corruptedLoader = new LazyFileLoader(corruptedConfigFile, tempDir);
2113 const document = new MockTextDocument('/test/test.ts');
2114 const editor = new MockTextEditor(document);
2115
2116 generator = new CommentGenerator(corruptedLoader, editor as any, mockRandomLogo);
2117
2118 const generatorAny = generator as any;
2119 const commentStyle = await generatorAny.determineCorrectComment();
2120
2121 assert.deepStrictEqual(commentStyle.singleLine, []);
2122 assert.deepStrictEqual(commentStyle.multiLine, []);
2123 });
2124
2129 test('should handle update without header bounds', async () => {
2130 const document = new MockTextDocument('/test/test.ts');
2131 const editor = new MockTextEditor(document);
2132
2133 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2134
2135 const generatorAny = generator as any;
2136 const comments = [' * ', ' * ', ' * '];
2137
2138 // Don't set header bounds
2139 await generatorAny.updateEditDate(editor, comments);
2140
2141 // Should not throw, should log error
2142 assert.ok(true);
2143 });
2144
2149 test('should handle undefined document in update', async () => {
2150 const document = new MockTextDocument('/test/test.ts');
2151 const editor = new MockTextEditor(document);
2152
2153 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2154
2155 const generatorAny = generator as any;
2156 const comments = [' * ', ' * ', ' * '];
2157
2158 // Clear document
2159 generatorAny.documentBody = undefined;
2160
2161 await generatorAny.updateEditDate(editor, comments);
2162
2163 assert.ok(true); // Should not throw
2164 });
2165
2170 test('should handle very long file paths', () => {
2171 const longPath = '/very/' + 'long/'.repeat(100) + 'path/to/file.ts';
2172 const document = new MockTextDocument(longPath);
2173 const editor = new MockTextEditor(document);
2174
2175 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2176
2177 const generatorAny = generator as any;
2178 assert.strictEqual(generatorAny.fileName, 'file.ts');
2179 assert.strictEqual(generatorAny.fileExtension, 'ts');
2180 });
2181
2186 test('should handle empty file names', () => {
2187 const document = new MockTextDocument('/test/');
2188 const editor = new MockTextEditor(document);
2189
2190 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2191
2192 const generatorAny = generator as any;
2193 assert.strictEqual(generatorAny.fileName, 'unknown');
2194 });
2195
2200 test('should handle special characters in file names', () => {
2201 const specialPath = '/test/file with spaces & symbols!.ts';
2202 const document = new MockTextDocument(specialPath);
2203 const editor = new MockTextEditor(document);
2204
2205 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2206
2207 const generatorAny = generator as any;
2208 assert.strictEqual(generatorAny.fileName, 'file with spaces & symbols!.ts');
2209 assert.strictEqual(generatorAny.fileExtension, 'ts');
2210 });
2211 });
2212
2220 suite('Integration Tests', () => {
2225 test('should complete full header injection workflow', async () => {
2226 const document = new MockTextDocument('/test/main.ts', '', 'typescript');
2227 const editor = new MockTextEditor(document);
2228
2229 mockActiveTextEditor = editor as any;
2230 mockShowInputBoxResponse = 'Main application entry point';
2231
2232 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2233
2234 await generator.injectHeader();
2235
2236 // Verify header was written
2237 assert.strictEqual(mockWorkspaceEdits.length, 1);
2238 });
2239
2244 test('should handle complete refresh workflow with existing header', async () => {
2245 const existingHeader = `/*
2246 * ═══════════════════════ ◄ BEGIN AsperHeader ► ═══════════════════════
2247 * Project: AsperHeader
2248 * File: main.ts
2249 * Created: 01-10-2025
2250 * Last Modified: 14:25:30 01-10-2025
2251 * ═══════════════════════ ◄ END AsperHeader ► ═══════════════════════
2252 */
2253
2254const app = 'Hello World';`;
2255
2256 const document = new MockTextDocument('/test/main.ts', existingHeader, 'typescript');
2257 const editor = new MockTextEditor(document);
2258
2259 mockActiveTextEditor = editor as any;
2260
2261 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2262
2263 await generator.refreshHeader(document as any);
2264
2265 // Should have updated the timestamp if refresh is enabled
2266 assert.ok(true); // Test passes if no errors
2267 });
2268
2273 test('should handle multiple rapid operations', async () => {
2274 const document = new MockTextDocument('/test/rapid.ts');
2275 const editor = new MockTextEditor(document);
2276
2277 mockActiveTextEditor = editor as any;
2278 mockShowInputBoxResponse = 'Rapid test';
2279
2280 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2281
2282 // Simulate rapid calls
2283 const promises = [
2284 generator.injectHeader(),
2285 generator.refreshHeader(document as any),
2286 generator.injectHeader()
2287 ];
2288
2289 await Promise.all(promises);
2290
2291 // Should handle concurrent operations gracefully
2292 assert.ok(true);
2293 });
2294 });
2295});
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 mockWorkspaceEdits
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
export const export const string[]
Definition constants.ts:203
import *as vscode from vscode
Definition extension.ts:45
CipherI from base
Definition index.ts:13
Represents a single line within a mock VS Code document for testing.
Structure representing a loaded ASCII art logo with metadata.
export const CodeConfig
Exported configuration singleton for extension-wide access @export Primary configuration interface us...