Asper Header  1.0.16
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 const headerContent = `/*
1291 * ═══════════════════════ ◄ BEGIN TestProject ► ═══════════════════════
1292 * Logo:
1293 * ▄▄▄▄▄▄▄▄
1294 * ───────
1295 * Project: TestProject
1296 * File: test.ts
1297 * Created: 03-10-2025
1298 * LAST Modified: 15:30:45 03-10-2025
1299 * Description:
1300 * Test file
1301 * ───────
1302 * Copyright: © 2025
1303 * Purpose: Testing
1304 * ═══════════════════════ ◄ END TestProject ► ═══════════════════════
1305 */`;
1306
1307 const document = new MockTextDocument('/test/test.ts', headerContent);
1308 const editor = new MockTextEditor(document);
1309 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1310
1311 const generatorAny = generator as any;
1312 const comments = ['/* ', ' * ', ' */'];
1313
1314 // Simulate finding header bounds
1315 generatorAny.headerInnerStart = 1;
1316 generatorAny.headerInnerEnd = 15;
1317
1318 await generatorAny.updateEditDate(document, comments);
1319
1320 assert.strictEqual(mockWorkspaceEdits.length, 1);
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(mockWorkspaceEdits.length > 0);
1364 });
1365
1370 test('should refresh header when configured', async () => {
1371 const headerContent = `/*
1372 * ═══════════════════════ ◄ BEGIN TestProject ► ═══════════════════════
1373 * Project: TestProject
1374 * File: test.ts
1375 * Last Modified: 15:30:45 03-10-2025
1376 * ═══════════════════════ ◄ END TestProject ► ═══════════════════════
1377 */`;
1378
1379 const document = new MockTextDocument('/test/test.ts', headerContent);
1380 const editor = new MockTextEditor(document);
1381
1382 mockActiveTextEditor = editor as any;
1383
1384 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1385
1386 await generator.refreshHeader(document as any);
1387
1388 // Should have updated timestamp if refresh is enabled
1389 // (depends on configuration, so we just test it doesn't throw)
1390 assert.ok(true);
1391 });
1392
1397 test('should handle refresh with no document', async () => {
1398 generator = new CommentGenerator(lazyFileLoader);
1399
1400 await generator.refreshHeader(undefined);
1401
1402 assert.ok(true); // Should not throw
1403 });
1404
1409 test('should handle refresh with closed document', async () => {
1410 const document = new MockTextDocument('/test/test.ts', '', 'typescript', vscode.EndOfLine.LF, true);
1411 const editor = new MockTextEditor(document);
1412
1413 mockActiveTextEditor = editor as any;
1414
1415 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1416
1417 await generator.refreshHeader(document as any);
1418
1419 assert.ok(true); // Should not throw
1420 });
1421 });
1422
1430 suite('Language Customization Features', () => {
1435 test('should trim trailing spaces when enabled', () => {
1436 const document = new MockTextDocument('/test/test.ts');
1437 const editor = new MockTextEditor(document);
1438 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1439
1440 const generatorAny = generator as any;
1441 generatorAny.trimTrailingSpaces = true;
1442
1443 const result = generatorAny.mySmartTrimmer('content with spaces ');
1444 assert.strictEqual(result, 'content with spaces');
1445 });
1446
1451 test('should preserve trailing spaces when disabled', () => {
1452 const document = new MockTextDocument('/test/test.ts');
1453 const editor = new MockTextEditor(document);
1454 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1455
1456 const generatorAny = generator as any;
1457 generatorAny.trimTrailingSpaces = false;
1458
1459 const result = generatorAny.mySmartTrimmer('content with spaces ');
1460 assert.strictEqual(result, 'content with spaces ');
1461 });
1462
1467 test('should prepend language-specific text when configured', () => {
1468 const document = new MockTextDocument('/test/test.py', '', 'python');
1469 const editor = new MockTextEditor(document);
1470 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1471
1472 const generatorAny = generator as any;
1473 generatorAny.languagePrepend = { python: '#!/usr/bin/env python\n' };
1474
1475 let buildHeader: string[] = [];
1476 buildHeader = generatorAny.prependIfPresent(buildHeader, vscode.EndOfLine.LF, 'python');
1477
1478 assert.strictEqual(buildHeader.length, 1);
1479 assert.strictEqual(buildHeader[0], '#!/usr/bin/env python\n');
1480 });
1481
1486 test('should handle array prepend content', () => {
1487 const document = new MockTextDocument('/test/test.py', '', 'python');
1488 const editor = new MockTextEditor(document);
1489 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1490
1491 const generatorAny = generator as any;
1492 generatorAny.languagePrepend = { python: ['#!/usr/bin/env python', '# -*- coding: utf-8 -*-'] };
1493
1494 let buildHeader: string[] = [];
1495 buildHeader = generatorAny.prependIfPresent(buildHeader, vscode.EndOfLine.LF, 'python');
1496
1497 assert.strictEqual(buildHeader.length, 1);
1498 assert.ok(buildHeader[0].includes('#!/usr/bin/env python'));
1499 });
1500
1505 test('should skip prepend when language is undefined', () => {
1506 const document = new MockTextDocument('/test/test.ts');
1507 const editor = new MockTextEditor(document);
1508 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1509
1510 const generatorAny = generator as any;
1511 generatorAny.languagePrepend = { python: '#!/usr/bin/env python\n' };
1512
1513 let buildHeader: string[] = [];
1514 buildHeader = generatorAny.prependIfPresent(buildHeader, vscode.EndOfLine.LF, undefined);
1515
1516 assert.strictEqual(buildHeader.length, 0);
1517 });
1518
1523 test('should append language-specific text when configured', () => {
1524 const document = new MockTextDocument('/test/test.py', '', 'python');
1525 const editor = new MockTextEditor(document);
1526 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1527
1528 const generatorAny = generator as any;
1529 generatorAny.languageAppend = { python: '\n# Code begins\n' };
1530
1531 let buildHeader: string[] = [];
1532 buildHeader = generatorAny.appendIfPresent(buildHeader, vscode.EndOfLine.LF, 'python');
1533
1534 assert.strictEqual(buildHeader.length, 1);
1535 assert.strictEqual(buildHeader[0], '\n# Code begins\n');
1536 });
1537
1542 test('should handle array append content', () => {
1543 const document = new MockTextDocument('/test/test.py', '', 'python');
1544 const editor = new MockTextEditor(document);
1545 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1546
1547 const generatorAny = generator as any;
1548 generatorAny.languageAppend = { python: ['', '# Code begins', '# ============'] };
1549
1550 let buildHeader: string[] = [];
1551 buildHeader = generatorAny.appendIfPresent(buildHeader, vscode.EndOfLine.LF, 'python');
1552
1553 assert.strictEqual(buildHeader.length, 1);
1554 assert.ok(buildHeader[0].includes('# Code begins'));
1555 });
1556
1561 test('should skip append when language is undefined', () => {
1562 const document = new MockTextDocument('/test/test.ts');
1563 const editor = new MockTextEditor(document);
1564 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1565
1566 const generatorAny = generator as any;
1567 generatorAny.languageAppend = { python: '\n# Code begins\n' };
1568
1569 let buildHeader: string[] = [];
1570 buildHeader = generatorAny.appendIfPresent(buildHeader, vscode.EndOfLine.LF, undefined);
1571
1572 assert.strictEqual(buildHeader.length, 0);
1573 });
1574
1579 test('should apply single-line comment override when configured', () => {
1580 const document = new MockTextDocument('/test/test.idr', '', 'idris');
1581 const editor = new MockTextEditor(document);
1582 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1583
1584 const generatorAny = generator as any;
1585 generatorAny.singleLineOverride = { idris: '|||' };
1586
1587 const commentStyle = {
1588 singleLine: ['--'],
1589 multiLine: [],
1590 prompt_comment_opening_type: false,
1591 language: 'idris'
1592 };
1593
1594 const result = generatorAny.getOverrideIfPresent(commentStyle);
1595
1596 assert.deepStrictEqual(result.singleLine, ['|||']);
1597 assert.strictEqual(result.language, 'idris');
1598 });
1599
1604 test('should apply multi-line comment override when configured', () => {
1605 const document = new MockTextDocument('/test/test.c', '', 'c');
1606 const editor = new MockTextEditor(document);
1607 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1608
1609 const generatorAny = generator as any;
1610 generatorAny.multiLineOverride = { c: ['/*', '**', '*/'] };
1611
1612 const commentStyle = {
1613 singleLine: ['//'],
1614 multiLine: ['/*', ' *', ' */'],
1615 prompt_comment_opening_type: false,
1616 language: 'c'
1617 };
1618
1619 const result = generatorAny.getOverrideIfPresent(commentStyle);
1620
1621 assert.deepStrictEqual(result.multiLine, ['/*', '**', '*/']);
1622 assert.strictEqual(result.language, 'c');
1623 });
1624
1629 test('should handle array single-line comment override', () => {
1630 const document = new MockTextDocument('/test/test.idr', '', 'idris');
1631 const editor = new MockTextEditor(document);
1632 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1633
1634 const generatorAny = generator as any;
1635 generatorAny.singleLineOverride = { idris: ['|||', '---'] };
1636
1637 const commentStyle = {
1638 singleLine: ['--'],
1639 multiLine: [],
1640 prompt_comment_opening_type: false,
1641 language: 'idris'
1642 };
1643
1644 const result = generatorAny.getOverrideIfPresent(commentStyle);
1645
1646 assert.deepStrictEqual(result.singleLine, ['|||', '---']);
1647 });
1648
1653 test('should skip override when language is undefined', () => {
1654 const document = new MockTextDocument('/test/test.ts');
1655 const editor = new MockTextEditor(document);
1656 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1657
1658 const generatorAny = generator as any;
1659 generatorAny.singleLineOverride = { idris: '|||' };
1660
1661 const commentStyle = {
1662 singleLine: ['--'],
1663 multiLine: [],
1664 prompt_comment_opening_type: false,
1665 language: undefined
1666 };
1667
1668 const result = generatorAny.getOverrideIfPresent(commentStyle);
1669
1670 assert.deepStrictEqual(result.singleLine, ['--']);
1671 assert.strictEqual(result.language, undefined);
1672 });
1673
1678 test('should prefer single-line comments when configured', async () => {
1679 const document = new MockTextDocument('/test/test.ts', '', 'typescript');
1680 const editor = new MockTextEditor(document);
1681 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1682
1683 const generatorAny = generator as any;
1684 generatorAny.preferSingleLineComments = true;
1685
1686 const commentStyle = {
1687 singleLine: ['//'],
1688 multiLine: ['/*', ' *', ' */'],
1689 prompt_comment_opening_type: false,
1690 language: 'typescript'
1691 };
1692
1693 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1694
1695 // All three prefixes should be the same (single-line)
1696 assert.ok(prefixes[0].includes('//'));
1697 assert.ok(prefixes[1].includes('//'));
1698 assert.ok(prefixes[2].includes('//'));
1699 });
1700
1705 test('should use multi-line comments by default', async () => {
1706 const document = new MockTextDocument('/test/test.ts', '', 'typescript');
1707 const editor = new MockTextEditor(document);
1708 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1709
1710 const generatorAny = generator as any;
1711 generatorAny.preferSingleLineComments = false;
1712
1713 const commentStyle = {
1714 singleLine: ['//'],
1715 multiLine: ['/*', ' *', ' */'],
1716 prompt_comment_opening_type: false,
1717 language: 'typescript'
1718 };
1719
1720 const prefixes = await generatorAny.getCorrectCommentPrefix(commentStyle);
1721
1722 // Should use multi-line style
1723 assert.ok(prefixes[0].includes('/*'));
1724 assert.ok(prefixes[1].includes(' *'));
1725 assert.ok(prefixes[2].includes(' */'));
1726 });
1727 });
1728
1735 suite('Shebang Detection', () => {
1740 test('should detect bash shebang and skip first line', () => {
1741 const content = '#!/bin/bash\necho "Hello World"';
1742 const document = new MockTextDocument('/test/script.sh', content, 'shellscript');
1743 const editor = new MockTextEditor(document);
1744 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1745
1746 const generatorAny = generator as any;
1747 const insertLine = generatorAny.skipFirstLineInDocument(document);
1748
1749 assert.strictEqual(insertLine, 1);
1750 });
1751
1756 test('should detect python shebang and skip first line', () => {
1757 const content = '#!/usr/bin/env python3\nprint("Hello World")';
1758 const document = new MockTextDocument('/test/script.py', content, 'python');
1759 const editor = new MockTextEditor(document);
1760 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1761
1762 const generatorAny = generator as any;
1763 const insertLine = generatorAny.skipFirstLineInDocument(document);
1764
1765 assert.strictEqual(insertLine, 1);
1766 });
1767
1772 test('should insert at line 0 when no shebang present', () => {
1773 const content = 'import sys\nprint("Hello World")';
1774 const document = new MockTextDocument('/test/script.py', content, 'python');
1775 const editor = new MockTextEditor(document);
1776 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1777
1778 const generatorAny = generator as any;
1779 const insertLine = generatorAny.skipFirstLineInDocument(document);
1780
1781 assert.strictEqual(insertLine, 0);
1782 });
1783
1788 test('should handle empty document', () => {
1789 const document = new MockTextDocument('/test/empty.sh', '', 'shellscript');
1790 const editor = new MockTextEditor(document);
1791 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1792
1793 const generatorAny = generator as any;
1794 const insertLine = generatorAny.skipFirstLineInDocument(document);
1795
1796 assert.strictEqual(insertLine, 0);
1797 });
1798
1803 test('should not treat regular hash comments as shebang', () => {
1804 const content = '# This is a comment\nprint("Hello")';
1805 const document = new MockTextDocument('/test/script.py', content, 'python');
1806 const editor = new MockTextEditor(document);
1807 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1808
1809 const generatorAny = generator as any;
1810 const insertLine = generatorAny.skipFirstLineInDocument(document);
1811
1812 assert.strictEqual(insertLine, 0);
1813 });
1814
1819 test('should detect shebang with arguments', () => {
1820 const content = '#!/usr/bin/env node --harmony\nconsole.log("Test")';
1821 const document = new MockTextDocument('/test/script.js', content, 'javascript');
1822 const editor = new MockTextEditor(document);
1823 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1824
1825 const generatorAny = generator as any;
1826 const insertLine = generatorAny.skipFirstLineInDocument(document);
1827
1828 assert.strictEqual(insertLine, 1);
1829 });
1830 });
1831
1832 suite('Error Handling and Edge Cases', () => {
1837 test('should handle missing language comment loader', async () => {
1838 const document = new MockTextDocument('/test/test.ts');
1839 const editor = new MockTextEditor(document);
1840
1841 generator = new CommentGenerator(undefined, editor as any, mockRandomLogo);
1842
1843 const generatorAny = generator as any;
1844 const commentStyle = await generatorAny.determineCorrectComment();
1845
1846 assert.deepStrictEqual(commentStyle.singleLine, []);
1847 assert.deepStrictEqual(commentStyle.multiLine, []);
1848 });
1849
1854 test('should handle corrupted language configuration', async () => {
1855 // Create corrupted config file
1856 const corruptedConfigFile = path.join(tempDir, 'corrupted.json');
1857 await fs.writeFile(corruptedConfigFile, '{"invalid": structure}');
1858
1859 const corruptedLoader = new LazyFileLoader(corruptedConfigFile, tempDir);
1860 const document = new MockTextDocument('/test/test.ts');
1861 const editor = new MockTextEditor(document);
1862
1863 generator = new CommentGenerator(corruptedLoader, editor as any, mockRandomLogo);
1864
1865 const generatorAny = generator as any;
1866 const commentStyle = await generatorAny.determineCorrectComment();
1867
1868 assert.deepStrictEqual(commentStyle.singleLine, []);
1869 assert.deepStrictEqual(commentStyle.multiLine, []);
1870 });
1871
1876 test('should handle update without header bounds', async () => {
1877 const document = new MockTextDocument('/test/test.ts');
1878 const editor = new MockTextEditor(document);
1879
1880 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1881
1882 const generatorAny = generator as any;
1883 const comments = [' * ', ' * ', ' * '];
1884
1885 // Don't set header bounds
1886 await generatorAny.updateEditDate(editor, comments);
1887
1888 // Should not throw, should log error
1889 assert.ok(true);
1890 });
1891
1896 test('should handle undefined document in update', async () => {
1897 const document = new MockTextDocument('/test/test.ts');
1898 const editor = new MockTextEditor(document);
1899
1900 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1901
1902 const generatorAny = generator as any;
1903 const comments = [' * ', ' * ', ' * '];
1904
1905 // Clear document
1906 generatorAny.documentBody = undefined;
1907
1908 await generatorAny.updateEditDate(editor, comments);
1909
1910 assert.ok(true); // Should not throw
1911 });
1912
1917 test('should handle very long file paths', () => {
1918 const longPath = '/very/' + 'long/'.repeat(100) + 'path/to/file.ts';
1919 const document = new MockTextDocument(longPath);
1920 const editor = new MockTextEditor(document);
1921
1922 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1923
1924 const generatorAny = generator as any;
1925 assert.strictEqual(generatorAny.fileName, 'file.ts');
1926 assert.strictEqual(generatorAny.fileExtension, 'ts');
1927 });
1928
1933 test('should handle empty file names', () => {
1934 const document = new MockTextDocument('/test/');
1935 const editor = new MockTextEditor(document);
1936
1937 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1938
1939 const generatorAny = generator as any;
1940 assert.strictEqual(generatorAny.fileName, 'unknown');
1941 });
1942
1947 test('should handle special characters in file names', () => {
1948 const specialPath = '/test/file with spaces & symbols!.ts';
1949 const document = new MockTextDocument(specialPath);
1950 const editor = new MockTextEditor(document);
1951
1952 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1953
1954 const generatorAny = generator as any;
1955 assert.strictEqual(generatorAny.fileName, 'file with spaces & symbols!.ts');
1956 assert.strictEqual(generatorAny.fileExtension, 'ts');
1957 });
1958 });
1959
1967 suite('Integration Tests', () => {
1972 test('should complete full header injection workflow', async () => {
1973 const document = new MockTextDocument('/test/main.ts', '', 'typescript');
1974 const editor = new MockTextEditor(document);
1975
1976 mockActiveTextEditor = editor as any;
1977 mockShowInputBoxResponse = 'Main application entry point';
1978
1979 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
1980
1981 await generator.injectHeader();
1982
1983 // Verify header was written
1984 assert.strictEqual(mockWorkspaceEdits.length, 1);
1985 });
1986
1991 test('should handle complete refresh workflow with existing header', async () => {
1992 const existingHeader = `/*
1993 * ═══════════════════════ ◄ BEGIN AsperHeader ► ═══════════════════════
1994 * Project: AsperHeader
1995 * File: main.ts
1996 * Created: 01-10-2025
1997 * Last Modified: 14:25:30 01-10-2025
1998 * ═══════════════════════ ◄ END AsperHeader ► ═══════════════════════
1999 */
2000
2001const app = 'Hello World';`;
2002
2003 const document = new MockTextDocument('/test/main.ts', existingHeader, 'typescript');
2004 const editor = new MockTextEditor(document);
2005
2006 mockActiveTextEditor = editor as any;
2007
2008 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2009
2010 await generator.refreshHeader(document as any);
2011
2012 // Should have updated the timestamp if refresh is enabled
2013 assert.ok(true); // Test passes if no errors
2014 });
2015
2020 test('should handle multiple rapid operations', async () => {
2021 const document = new MockTextDocument('/test/rapid.ts');
2022 const editor = new MockTextEditor(document);
2023
2024 mockActiveTextEditor = editor as any;
2025 mockShowInputBoxResponse = 'Rapid test';
2026
2027 generator = new CommentGenerator(lazyFileLoader, editor as any, mockRandomLogo);
2028
2029 // Simulate rapid calls
2030 const promises = [
2031 generator.injectHeader(),
2032 generator.refreshHeader(document as any),
2033 generator.injectHeader()
2034 ];
2035
2036 await Promise.all(promises);
2037
2038 // Should handle concurrent operations gracefully
2039 assert.ok(true);
2040 });
2041 });
2042});
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
iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAAG0OVFdAAAAv3pUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjabVBbEsMgCPznFD2CAho5jknTmd6gx qtNJMEqCKH4h lRj6dADxVZugptTqwzYer65UeIe5DWUctXFzIXEu5EdIHqrWYry UL6xZmlG7UnJa57b oHlEkAAAGEaUNDUElDQyBwcm9maWxlAAB4nH2RPUjDUBSFT1OlKhWHFhFxyFA72UVFHGsVilAh1AqtOpi89A aNCQpLo6Ca8HBn8Wqg4uzrg6ugiD4A IuOCm6SIn3JYUWsT64vI z3jncdx8gNCpMs3rigKbbZjqZELO5VTHwin4MU4UQlZllzElSCl3X1z18fL L8azu9 Ma56LLAM8NmJj1PHCYWix2sdDArmRrxNHFE1XTKF7Ieq5y3OGuVGmv1yV8YzOsry1ynGkMSi1iCBBEKaiijAhsx2nVSLKTpPNHFP r6JXIp5CqDkWMBVWiQXT yerVWYmvSSggmg98VxPsaBwC7QrDvO97HjNE8A zNwpbf91QYw KqFQAN7P6JtyQOgWGFjz5tY6x kDkKFZpW6Ag0MgWqTs9S7v7uuc2793WvP7AZKlcrMQx gGAAANcmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6R0lNUD0iaHR0cDovL3d3dy5naW1wLm9yZy94bXAvIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDowOWQ3ODk3OS03YjNiLTRhMTgtODM5ZS1lMDgwOGNjMmUzY2EiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTM2YWE3YmMtN2QzZS00ZDJkLWIxMjItYTFhZjQwMjkzYjI5IgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6OTc2ZWEzNTgtNTNlNy00ODZkLWIzMzQtMjhmZWY1N2IyZWIzIgogICBHSU1QOkFQST0iMy4wIgogICBHSU1QOlBsYXRmb3JtPSJMaW51eCIKICAgR0lNUDpUaW1lU3RhbXA9IjE3NTg1MzI3OTM2NzA4NzIiCiAgIEdJTVA6VmVyc2lvbj0iMy4wLjQiCiAgIGRjOkZvcm1hdD0iaW1hZ2UvcG5nIgogICB0aWZmOk9yaWVudGF0aW9uPSIxIgogICB4bXA6Q3JlYXRvclRvb2w9IkdJTVAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjU6MDk6MjJUMTE6MTk6NTArMDI6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI1OjA5OjIyVDExOjE5OjUwKzAyOjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZGE1MDJhZWEtNDM0MS00NjdjLTgwMzEtYjUwMmU2OGFhYjkwIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHSU1QIDMuMC40IChMaW51eCkiCiAgICAgIHN0RXZ0OndoZW49IjIwMjUtMDktMjJUMTE6MTk6NTMrMDI6MDAiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSJ3Ij8 QZaInQAAAAZiS0dEAAAAAAAA UO7fwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB kJFgkTNb1wNOYAABokSURBVHja7V15dFRF1r9V7 WW7mwsAZJ0SJpElARhUBM0gKNwjsM2gIMw oF Ss4MHy5DmBEB56g4Is6ZIQGRJYownzIalCUzLnj0Cx7GBZhAWISIMGaTYIBgku5Od7 t7vdH8pruTifpzkaAd8 pk8573fWqbv3q3lu3bt1HEPEkAAyAThLf8uNBna2AQhfJW4EkSV2rgOd5SEpKavWF yv QjEZjq58rigLJyclAqfdZZMaMGbX5 fmDAQAIItb2jbnQWVK70KUK8Kp2IWAuYOcrSEpKAkJIwG0GQ5OtXekCBUDabss6qIABEAaEEKitrXVnZ2eLwaSy3w uPI2CLCtAgAMAgLFjx oqKyt59b7a3VZMbJYDzXJBp NAUZoFSWVlJa sKCgoK6NLgSXBSdOnGhseQAiImRnZ0u T21TFiAqmJgYj3PmzHF4ryiK7PuNDipAHDhwICIirly58kJ1daUz8L4XylarFX744YdW45CcnNzcmSvyAMrLy vqXOiKPBgEV4koXGXqmw0YOnQoDB06NKQK9u3bdz6YWdPpBqhGRWjoZsBQBkIxQBD12hA0y1vVBiOEgCIrYVmLXWxAs7z2teE4noPt27fbw7Gyg89PijA02RpETalWZ7N42rJlCzDlSl8kSYKpU6fqAAAEQYCHHnzIuWv3Lsvx48drb7311sGt6vO1zhARGWNotVrRarUiYwzbJwWLiz rSUyMR0QFRVFEH6mNR44caXK73aLH45EREQsKCjy JTMzEyPeiclJQVGjhwZYTQadZRSTpIkGDx4sCAIgtguB1TV0pZ6aY8DiIhlZWVNKjMREQVBUH7 ziBhdaEVB8KtINzva8qo29cH3dEAvKGHQGtA322AIivQG hs1QDGGAwdOhQoR0OSclarFbKysuq7zyZk4dmEiAoQgu16tXoNA1fHKPUrV5kDhBCQJAkYY73VABZQAEpKShyKokB fr49JPM80ESSJRmtVqufW7A9kywhYQhmZd3xk2qSqYYtImJCQkKHNdC2etbsEWABrljFb UkCBLwvB4I4bzM3L17d OpU6caEBFmzZrlfuaZZ wqOIOBlA JiCQnJLkddu0tS7Ytm2brCgKL8uy99Y777xD33vvvUhCCJyvOS lpqUq fv MREBE3btzoREQ8ePCgK3Ct0U4DWEgLE98GFBYWlvvenTRpknP8 PGNKp6C1dnFBiAmJibi7bff lPg9dTUVCmwjtWrVzcsW7bM3o3rAn9yu91gMplAkRU4 e1pXpZl0Ol03vsVFRVcQUGBpV05QCgJw RmV1bIwKCkpMSh1sHxnPfhJ0 etCMiBD682yXhkiVLdOqqWd0tYYzBiBEjotrqTLfoggiTBQAoHD582Hju3Dk3YzJyHIGH uvXrltuucXruAjJKmaMQXJyMlRVVYVsDwRzdQc2dO3atbWzZs1q5R g21KrgQBqiziO83 U16fK Bgc7OFtrgsCvdo9uTjVFiZaA7QGXPX9ght BDQGaAzQGKAx4GpSyEuSr7 Kysru7xDodK ffvOP LII GEEBgyZEj9oUOHYvsUAhDR6zP0vdZ1BjQvWhjKgKgA5Tg PyMi6ZR5r8mAnpIBPcZ7NegSA pSuSFfxTtzTffbLbZbKb09HRp0aJF5mPHjkWkp6db3n777foZM2Y4rxIDWBil60j45JNP6jdv3txYVVVVDwCg1 th vz5sRcvXoTDhw b6 rq3L0kAxjwPA83DU8FURSDOmWuqAraLq8REbZt2ybzvA4o5QGZKvlbj8mAAQN0c fONZ49e9Yv5Pf2228X586d22 hfqX3ieB47j DYcgu3tuN1uZenSpdx export const string
Definition constants.ts:258
import *as vscode from vscode
Definition extension.ts:45
Represents a single line within a mock VS Code document for testing.
export const CodeConfig
Exported configuration singleton for extension-wide access @export Primary configuration interface us...