Asper Header  1.0.14
The header injector extension
Loading...
Searching...
No Matches
darling.test.ts
Go to the documentation of this file.
1
32import * as assert from 'assert';
33import * as vscode from 'vscode';
34import * as fs from 'fs/promises';
35import * as path from 'path';
36import * as os from 'os';
37import { Darling, Person } from '../modules/darling';
38
43interface MockCharacterData {
44 id: number;
45 name: string;
46 japanese_name?: string;
47 romaji: string;
48 age: string;
49 quote: string;
50 description: string;
51 image_link: string[];
52 height: string;
53 weight: string;
54 more_information: string;
56 alias?: string[] | null;
57}
58
64 private _html: string = '';
65 private messageHandlers: Array<(message: any) => void> = [];
66
67 get html(): string {
68 return this._html;
69 }
70
71 set html(value: string) {
72 this._html = value;
73 }
74
80 onDidReceiveMessage(handler: (message: any) => void) {
81 this.messageHandlers.push(handler);
82 return { dispose: () => { } };
83 }
84
89 postMessage(message: any) {
90 this.messageHandlers.forEach(handler => handler(message));
91 }
92}
93
102suite('Darling Test Suite', () => {
103 let tempDir: string;
104 let testFilePath: string;
105 let mockCharacters: MockCharacterData[];
106 let originalCreateWebviewPanel: typeof vscode.window.createWebviewPanel;
107 let capturedHtml: string;
108 let lastCreatedWebview: MockWebview;
109
118 setup(async () => {
119 // Create temporary directory for test files
120 tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'darling-test-'));
121 testFilePath = path.join(tempDir, 'test-characters.json');
122
123 // Create comprehensive mock character data
124 mockCharacters = [
125 {
126 id: 1,
127 name: "Zero Two",
128 japanese_name: "ゼロツー",
129 romaji: "Zero Tsuu",
130 age: "Classified",
131 quote: "I want to become human because I have someone I want to be human for.",
132 description: "A hybrid human-klaxosaur who pilots with Hiro as his partner.",
133 image_link: [
134 " **** ",
135 " ** ** ",
136 " * ^ ^ * ",
137 "* < *",
138 " * --- * ",
139 " ** ** ",
140 " **** "
141 ],
142 height: "170 cm",
143 weight: "55 kg",
144 more_information: "https://example.com/zero-two",
145 type: "Klaxo-Sapien Hybrid",
146 alias: ["002", "Darling's Partner", "Oni"]
147 },
148 {
149 id: 2,
150 name: "Hiro",
151 romaji: "Hiro",
152 age: "16",
153 quote: "I want to pilot with Zero Two forever.",
154 description: "Former prodigy pilot who lost his abilities but regained them with Zero Two.",
155 image_link: [
156 " ###### ",
157 " # # ",
158 "# o o #",
159 "# - #",
160 " # == # ",
161 " ###### "
162 ],
163 height: "168 cm",
164 weight: "58 kg",
165 more_information: "https://example.com/hiro",
166 type: "Human",
167 alias: ["016", "Code 016"]
168 },
169 {
170 id: 3,
171 name: "Ichigo",
172 romaji: "Ichigo",
173 age: "16",
174 quote: "I'll protect everyone, no matter what it takes.",
175 description: "Squad 13's leader with strong leadership skills and deep care for her teammates.",
176 image_link: [
177 " @@@@@@ ",
178 " @ @ ",
179 "@ > < @",
180 "@ -- @",
181 " @ __ @ ",
182 " @@@@@@ "
183 ],
184 height: "159 cm",
185 weight: "48 kg",
186 more_information: "https://example.com/ichigo",
187 type: "Human",
188 alias: null
189 }
190 ];
191
192 // Setup webview mocking
193 capturedHtml = '';
194 originalCreateWebviewPanel = vscode.window.createWebviewPanel;
195 vscode.window.createWebviewPanel = (viewType: string, title: string, showOptions: any, options?: any) => {
196 lastCreatedWebview = new MockWebview();
197
198 // Capture HTML when it's set
199 Object.defineProperty(lastCreatedWebview, 'html', {
200 get: () => capturedHtml,
201 set: (value: string) => {
202 capturedHtml = value;
203 }
204 });
205
206 return {
207 title,
208 webview: lastCreatedWebview
209 } as any;
210 };
211 });
212
221 teardown(async () => {
222 // Cleanup temporary directory
223 try {
224 await fs.rm(tempDir, { recursive: true, force: true });
225 } catch (error) {
226 // Ignore cleanup errors
227 }
228
229 // Restore original VS Code API
230 if (originalCreateWebviewPanel) {
231 vscode.window.createWebviewPanel = originalCreateWebviewPanel;
232 }
233
234 // Reset captured data
235 capturedHtml = '';
236 });
237
245 suite('Constructor and Initialization', () => {
250 test('should create instance with default parameters', () => {
251 const darling = new Darling();
252 assert.ok(darling instanceof Darling, 'Should create Darling instance');
253 });
254
259 test('should create instance with file path parameter', async () => {
260 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
261 const darling = new Darling(testFilePath);
262 assert.ok(darling instanceof Darling, 'Should create Darling instance with file path');
263 });
264
269 test('should create instance with both file path and working directory', async () => {
270 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
271 const darling = new Darling(testFilePath, tempDir);
272 assert.ok(darling instanceof Darling, 'Should create Darling instance with both parameters');
273 });
274
279 test('should handle undefined parameters gracefully', () => {
280 const darling = new Darling(undefined, undefined);
281 assert.ok(darling instanceof Darling, 'Should handle undefined parameters');
282 });
283 });
284
291 suite('File Path Management', () => {
296 test('should update file path successfully', async () => {
297 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
298 const darling = new Darling();
299
300 const success = await darling.updateFilePath(testFilePath);
301 assert.strictEqual(success, true, 'Should return true for successful file path update');
302 });
303
308 test('should update working directory successfully', async () => {
309 const darling = new Darling();
310
311 const success = await darling.updateCurrentWorkingDirectory(tempDir);
312 assert.strictEqual(success, true, 'Should return true for successful working directory update');
313 });
314
319 test('should handle relative paths with working directory', async () => {
320 const relativePath = 'characters.json';
321 const fullPath = path.join(tempDir, relativePath);
322 await fs.writeFile(fullPath, JSON.stringify(mockCharacters));
323
324 const darling = new Darling();
325 await darling.updateCurrentWorkingDirectory(tempDir);
326 const success = await darling.updateFilePath(relativePath);
327
328 assert.strictEqual(success, true, 'Should handle relative paths correctly');
329 });
330 });
331
338 suite('Character Data Loading and Validation', () => {
343 test('should load and parse character data correctly', async () => {
344 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
345 const darling = new Darling(testFilePath, tempDir);
346
347 const person = await darling.getRandomPerson();
348
349 // Validate person structure
350 assert.ok(typeof person.id === 'number', 'Person should have numeric id');
351 assert.ok(typeof person.name === 'string', 'Person should have string name');
352 assert.ok(typeof person.romaji === 'string', 'Person should have string romaji');
353 assert.ok(typeof person.age === 'string', 'Person should have string age');
354 assert.ok(typeof person.quote === 'string', 'Person should have string quote');
355 assert.ok(typeof person.description === 'string', 'Person should have string description');
356 assert.ok(Array.isArray(person.imageContent), 'Person should have array imageContent');
357 assert.ok(typeof person.height === 'string', 'Person should have string height');
358 assert.ok(typeof person.weight === 'string', 'Person should have string weight');
359 assert.ok(typeof person.more_information === 'string', 'Person should have string more_information');
360 assert.ok(typeof person.type === 'string', 'Person should have string type');
361 });
362
367 test('should correctly map image_link to imageContent', async () => {
368 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
369 const darling = new Darling(testFilePath, tempDir);
370
371 const person = await darling.getRandomPerson();
372
373 assert.ok(Array.isArray(person.imageContent), 'imageContent should be an array');
374 assert.ok(person.imageContent.length > 0, 'imageContent should not be empty');
375 assert.ok(person.imageContent.every(line => typeof line === 'string'), 'All imageContent lines should be strings');
376 });
377
382 test('should handle null alias correctly', async () => {
383 const charactersWithNullAlias = mockCharacters.map(char => ({ ...char, alias: null }));
384 await fs.writeFile(testFilePath, JSON.stringify(charactersWithNullAlias));
385 const darling = new Darling(testFilePath, tempDir);
386
387 const person = await darling.getRandomPerson();
388
389 assert.strictEqual(person.alias, null, 'Should handle null alias correctly');
390 });
391
396 test('should handle empty image_link array', async () => {
397 const charactersWithEmptyImage = mockCharacters.map(char => ({ ...char, image_link: [] }));
398 await fs.writeFile(testFilePath, JSON.stringify(charactersWithEmptyImage));
399 const darling = new Darling(testFilePath, tempDir);
400
401 const person = await darling.getRandomPerson();
402
403 assert.ok(Array.isArray(person.imageContent), 'imageContent should be an array');
404 assert.strictEqual(person.imageContent.length, 0, 'imageContent should be empty array');
405 });
406 });
407
414 suite('Random Selection Algorithm', () => {
419 test('should select different characters on multiple calls', async () => {
420 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
421 const darling = new Darling(testFilePath, tempDir);
422
423 const selectedIds = new Set<number>();
424 const iterations = 20; // Run enough times to likely get different characters
425
426 for (let i = 0; i < iterations; i++) {
427 const person = await darling.getRandomPerson();
428 selectedIds.add(person.id);
429
430 // Verify selected person is from our mock data
431 const isValidCharacter = mockCharacters.some(char => char.id === person.id);
432 assert.ok(isValidCharacter, `Selected character with id ${person.id} should be from mock data`);
433 }
434
435 // With 3 characters and 20 iterations, we should get some variety
436 assert.ok(selectedIds.size >= 1, 'Should select at least one character');
437 });
438
443 test('should always return valid character from dataset', async () => {
444 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
445 const darling = new Darling(testFilePath, tempDir);
446
447 const iterations = 10;
448 for (let i = 0; i < iterations; i++) {
449 const person = await darling.getRandomPerson();
450
451 // Check that the returned person matches one from our dataset
452 const matchingCharacter = mockCharacters.find(char => char.id === person.id);
453 assert.ok(matchingCharacter, 'Selected character should exist in dataset');
454 assert.strictEqual(person.name, matchingCharacter.name, 'Character name should match dataset');
455 assert.strictEqual(person.romaji, matchingCharacter.romaji, 'Character romaji should match dataset');
456 }
457 });
458
463 test('should handle single character dataset', async () => {
464 const singleCharacter = [mockCharacters[0]];
465 await fs.writeFile(testFilePath, JSON.stringify(singleCharacter));
466 const darling = new Darling(testFilePath, tempDir);
467
468 const person1 = await darling.getRandomPerson();
469 const person2 = await darling.getRandomPerson();
470
471 assert.strictEqual(person1.id, person2.id, 'Should return same character when only one exists');
472 assert.strictEqual(person1.name, mockCharacters[0].name, 'Should return the single character');
473 });
474 });
475
482 suite('Error Handling and Edge Cases', () => {
487 test('should throw error for empty JSON file', async () => {
488 await fs.writeFile(testFilePath, '[]');
489 const darling = new Darling(testFilePath, tempDir);
490
491 try {
492 await darling.getRandomPerson();
493 assert.fail('Should throw error for empty character array');
494 } catch (error) {
495 assert.ok(error instanceof Error, 'Should throw Error instance');
496 assert.ok(error.message.length > 0, 'Error should have meaningful message');
497 }
498 });
499
504 test('should throw error for invalid JSON format', async () => {
505 await fs.writeFile(testFilePath, '{ invalid json }');
506 const darling = new Darling(testFilePath, tempDir);
507
508 try {
509 await darling.getRandomPerson();
510 assert.fail('Should throw error for invalid JSON');
511 } catch (error) {
512 assert.ok(error instanceof Error, 'Should throw Error instance for invalid JSON');
513 }
514 });
515
520 test('should throw error for non-array JSON content', async () => {
521 await fs.writeFile(testFilePath, '{"not": "an array"}');
522 const darling = new Darling(testFilePath, tempDir);
523
524 try {
525 await darling.getRandomPerson();
526 assert.fail('Should throw error for non-array JSON');
527 } catch (error) {
528 assert.ok(error instanceof Error, 'Should throw Error instance for non-array JSON');
529 }
530 });
531
536 test('should handle missing optional properties', async () => {
537 const minimalCharacter = [{
538 id: 999,
539 name: "Minimal Character",
540 romaji: "Minimal",
541 age: "Unknown",
542 quote: "Test quote",
543 description: "Test description",
544 // Missing image_link - should default to empty array
545 height: "Unknown",
546 weight: "Unknown",
547 more_information: "https://example.com",
548 type: "Test",
549 // Missing alias - should be undefined/null
550 }];
551
552 await fs.writeFile(testFilePath, JSON.stringify(minimalCharacter));
553 const darling = new Darling(testFilePath, tempDir);
554
555 const person = await darling.getRandomPerson();
556
557 assert.strictEqual(person.name, "Minimal Character", 'Should load character with missing properties');
558 assert.ok(Array.isArray(person.imageContent), 'imageContent should default to array');
559 assert.strictEqual(person.imageContent.length, 0, 'imageContent should be empty when image_link missing');
560 });
561 });
562
569 suite('HTML Content Generation', () => {
574 test('should generate valid HTML content for webview', async () => {
575 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
576 const darling = new Darling(testFilePath, tempDir);
577
578 // Mock the webview panel to capture HTML
579 let capturedHtml = '';
580 vscode.window.createWebviewPanel = (viewType: string, title: string, showOptions: any, options?: any) => {
581 const webviewMock = {
582 _html: '',
583 set html(value: string) { this._html = value; capturedHtml = value; },
584 get html() { return this._html; },
585 onDidReceiveMessage: () => ({ dispose: () => { } })
586 };
587 return {
588 webview: webviewMock
589 } as any;
590 };
591
592 await darling.displayRandomPersonInWindow();
593
594 // Validate HTML structure
595 assert.ok(capturedHtml.includes('<!DOCTYPE html>'), 'Should include DOCTYPE declaration');
596 assert.ok(capturedHtml.includes('<html lang="en">'), 'Should include HTML tag with language');
597 assert.ok(capturedHtml.includes('<head>'), 'Should include head section');
598 assert.ok(capturedHtml.includes('<body>'), 'Should include body section');
599 assert.ok(capturedHtml.includes('<meta charset="UTF-8">'), 'Should include charset meta tag');
600 });
601
606 test('should include character information in HTML', async () => {
607 await fs.writeFile(testFilePath, JSON.stringify([mockCharacters[0]])); // Use first character for predictable test
608 const darling = new Darling(testFilePath, tempDir);
609
610 await darling.displayRandomPersonInWindow();
611
612 // Check that character data is included
613 assert.ok(capturedHtml.includes('Zero Two'), 'Should include character name');
614 assert.ok(capturedHtml.includes('Zero Tsuu'), 'Should include character romaji');
615 assert.ok(capturedHtml.includes('Klaxo-Sapien Hybrid'), 'Should include character type');
616 assert.ok(capturedHtml.includes('I want to become human'), 'Should include character quote');
617 });
618
623 test('should include interactive buttons in HTML', async () => {
624 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
625 const darling = new Darling(testFilePath, tempDir);
626
627 await darling.displayRandomPersonInWindow();
628
629 // Check for interactive elements
630 assert.ok(capturedHtml.includes('id="copyBtn"'), 'Should include copy button');
631 assert.ok(capturedHtml.includes('id="zoomInBtn"'), 'Should include zoom in button');
632 assert.ok(capturedHtml.includes('id="zoomOutBtn"'), 'Should include zoom out button');
633 assert.ok(capturedHtml.includes('id="ascii"'), 'Should include ASCII art container');
634 });
635
640 test('should include CSS styling', async () => {
641 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
642 const darling = new Darling(testFilePath, tempDir);
643
644 await darling.displayRandomPersonInWindow();
645
646 // Check for CSS
647 assert.ok(capturedHtml.includes('<style>'), 'Should include style tag');
648 assert.ok(capturedHtml.includes('font-family'), 'Should include font styling');
649 assert.ok(capturedHtml.includes('white-space: pre'), 'Should include pre formatting for ASCII art');
650 });
651
656 test('should handle characters with null alias', async () => {
657 const characterWithNullAlias = [{ ...mockCharacters[0], alias: null }];
658 await fs.writeFile(testFilePath, JSON.stringify(characterWithNullAlias));
659 const darling = new Darling(testFilePath, tempDir);
660
661 await darling.displayRandomPersonInWindow();
662
663 assert.ok(capturedHtml.includes('None'), 'Should display "None" for null alias');
664 });
665 });
666
673 suite('JavaScript Functionality', () => {
678 test('should include copy button script', async () => {
679 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
680 const darling = new Darling(testFilePath, tempDir);
681
682 await darling.displayRandomPersonInWindow();
683
684 // Check for copy functionality
685 assert.ok(capturedHtml.includes("getElementById('copyBtn')"), 'Should include copy button handler');
686 assert.ok(capturedHtml.includes('navigator.clipboard.writeText'), 'Should include clipboard API usage');
687 assert.ok(capturedHtml.includes("getElementById('ascii')"), 'Should reference ASCII art element');
688 });
689
694 test('should include zoom functionality script', async () => {
695 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
696 const darling = new Darling(testFilePath, tempDir);
697
698 await darling.displayRandomPersonInWindow();
699
700 // Check for zoom functionality
701 assert.ok(capturedHtml.includes("getElementById('zoomInBtn')"), 'Should include zoom in button handler');
702 assert.ok(capturedHtml.includes("getElementById('zoomOutBtn')"), 'Should include zoom out button handler');
703 assert.ok(capturedHtml.includes('updateFontSize'), 'Should include font size update function');
704 assert.ok(capturedHtml.includes('currentSize'), 'Should include current size variable');
705 });
706
711 test('should include VS Code API integration', async () => {
712 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
713 const darling = new Darling(testFilePath, tempDir);
714
715 await darling.displayRandomPersonInWindow();
716
717 // Check for VS Code API usage
718 assert.ok(capturedHtml.includes('acquireVsCodeApi'), 'Should include VS Code API acquisition');
719 assert.ok(capturedHtml.includes('vscode.postMessage'), 'Should include message posting to extension');
720 });
721 });
722
729 suite('Integration and Message Handling', () => {
734 test('should setup message handler for copy events', async () => {
735 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
736 const darling = new Darling(testFilePath, tempDir);
737
738 let messageHandlerCalled = false;
739
740 vscode.window.createWebviewPanel = (viewType: string, title: string, showOptions: any, options?: any) => {
741 const webview = new MockWebview();
742 webview.onDidReceiveMessage = (handler: (message: any) => void) => {
743 messageHandlerCalled = true;
744 // Test the handler directly
745 try {
746 handler({ type: 'copied' });
747 handler({ type: 'unknown' });
748 } catch (error) {
749 // Should handle gracefully
750 }
751 return { dispose: () => { } };
752 };
753 return { webview } as any;
754 };
755
756 await darling.displayRandomPersonInWindow();
757
758 // Verify message handler was set up
759 assert.ok(messageHandlerCalled, 'Should setup message handler');
760 });
761
766 test('should create webview with correct parameters', async () => {
767 await fs.writeFile(testFilePath, JSON.stringify([mockCharacters[0]])); // Use specific character for predictable test
768 const darling = new Darling(testFilePath, tempDir);
769
770 let capturedTitle = '';
771 let capturedOptions: any = null;
772
773 vscode.window.createWebviewPanel = (viewType: string, title: string, showOptions: any, options?: any) => {
774 capturedTitle = title;
775 capturedOptions = options;
776 return {
777 webview: new MockWebview()
778 } as any;
779 };
780
781 await darling.displayRandomPersonInWindow();
782
783 assert.strictEqual(capturedTitle, 'Zero Two', 'Should use character name as panel title');
784 assert.ok(capturedOptions?.enableScripts, 'Should enable scripts in webview options');
785 });
786 });
787
794 suite('Performance and Memory Management', () => {
799 test('should handle multiple rapid character selections', async () => {
800 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
801 const darling = new Darling(testFilePath, tempDir);
802
803 const startTime = Date.now();
804 const iterations = 100;
805
806 for (let i = 0; i < iterations; i++) {
807 const person = await darling.getRandomPerson();
808 assert.ok(person.id, 'Should return valid person');
809 }
810
811 const endTime = Date.now();
812 const duration = endTime - startTime;
813
814 // Should complete reasonably quickly (less than 5 seconds for 100 iterations)
815 assert.ok(duration < 5000, `Should complete ${iterations} selections in reasonable time (took ${duration}ms)`);
816 });
817
822 test('should reuse file loader instance efficiently', async () => {
823 await fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
824 const darling = new Darling(testFilePath, tempDir);
825
826 // Multiple calls should reuse cached data
827 const person1 = await darling.getRandomPerson();
828 const person2 = await darling.getRandomPerson();
829 const person3 = await darling.getRandomPerson();
830
831 // All should return valid persons (caching should not break functionality)
832 assert.ok(person1.id, 'First person should be valid');
833 assert.ok(person2.id, 'Second person should be valid');
834 assert.ok(person3.id, 'Third person should be valid');
835 });
836
841 test('should handle large character datasets', async () => {
842 // Create a larger dataset for performance testing
843 const largeDataset: MockCharacterData[] = [];
844 for (let i = 1; i <= 1000; i++) {
845 largeDataset.push({
846 id: i,
847 name: `Character ${i}`,
848 romaji: `Character ${i}`,
849 age: `${16 + (i % 10)}`,
850 quote: `Quote from character ${i}`,
851 description: `Description for character ${i}`,
852 image_link: [`ASCII art line 1 for ${i}`, `ASCII art line 2 for ${i}`],
853 height: `${150 + (i % 30)} cm`,
854 weight: `${40 + (i % 40)} kg`,
855 more_information: `https://example.com/character${i}`,
856 type: 'Human',
857 alias: [`Alias${i}`]
858 });
859 }
860
861 await fs.writeFile(testFilePath, JSON.stringify(largeDataset));
862 const darling = new Darling(testFilePath, tempDir);
863
864 const startTime = Date.now();
865 const person = await darling.getRandomPerson();
866 const endTime = Date.now();
867
868 assert.ok(person.id >= 1 && person.id <= 1000, 'Should select from large dataset');
869 assert.ok(endTime - startTime < 1000, 'Should handle large dataset efficiently');
870 });
871 });
872});
Mock implementation of VS Code webview for testing.
postMessage(message:any)
Sends a message to all registered message handlers.
onDidReceiveMessage(handler:(message:any)=> void)
Registers a message handler for webview communication.
import *as vscode from vscode
import *as path from path
import *as os from os
import *as assert from assert
import fs from fs
Definition esbuild.js:9
Interface for test character data matching the JSON structure.
Represents a character from the Darling in the FranXX series.
import type
export const Record< string,(...args:any[])=> string