34import * as
fs from
'fs/promises';
35import * as
path from
'path';
36import * as
os from
'os';
37import { Darling,
Person } from
'../modules/darling';
56 alias?:
string[] |
null;
64 private _html:
string =
'';
65 private messageHandlers: Array<(message: any) =>
void> = [];
71 set html(value:
string) {
81 this.messageHandlers.push(handler);
82 return { dispose: () => { } };
90 this.messageHandlers.forEach(handler => handler(message));
102suite(
'Darling Test Suite', () => {
106 let originalCreateWebviewPanel: typeof
vscode.window.createWebviewPanel;
120 tempDir = await
fs.mkdtemp(
path.join(
os.tmpdir(),
'darling-test-'));
121 testFilePath =
path.join(tempDir,
'test-characters.json');
128 japanese_name:
"ゼロツー",
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.",
144 more_information:
"https://example.com/zero-two",
145 type:
"Klaxo-Sapien Hybrid",
146 alias: [
"002",
"Darling's Partner",
"Oni"]
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.",
165 more_information:
"https://example.com/hiro",
167 alias: [
"016",
"Code 016"]
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.",
186 more_information:
"https://example.com/ichigo",
194 originalCreateWebviewPanel =
vscode.window.createWebviewPanel;
195 vscode.window.createWebviewPanel = (viewType:
string, title:
string, showOptions: any, options?: any) => {
199 Object.defineProperty(lastCreatedWebview,
'html', {
200 get: () => capturedHtml,
202 capturedHtml = value;
208 webview: lastCreatedWebview
221 teardown(async () => {
224 await
fs.rm(tempDir, { recursive:
true, force:
true });
230 if (originalCreateWebviewPanel) {
231 vscode.window.createWebviewPanel = originalCreateWebviewPanel;
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');
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');
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');
279 test(
'should handle undefined parameters gracefully', () => {
280 const darling =
new Darling(undefined, undefined);
281 assert.ok(darling instanceof Darling,
'Should handle undefined parameters');
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();
300 const success = await darling.updateFilePath(testFilePath);
301 assert.strictEqual(success,
true,
'Should return true for successful file path update');
308 test(
'should update working directory successfully', async () => {
309 const darling =
new Darling();
311 const success = await darling.updateCurrentWorkingDirectory(tempDir);
312 assert.strictEqual(success,
true,
'Should return true for successful working directory update');
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));
324 const darling =
new Darling();
325 await darling.updateCurrentWorkingDirectory(tempDir);
326 const success = await darling.updateFilePath(relativePath);
328 assert.strictEqual(success,
true,
'Should handle relative paths correctly');
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);
347 const person = await darling.getRandomPerson();
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');
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);
371 const person = await darling.getRandomPerson();
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');
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);
387 const person = await darling.getRandomPerson();
389 assert.strictEqual(person.alias,
null,
'Should handle null alias correctly');
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);
401 const person = await darling.getRandomPerson();
403 assert.ok(Array.isArray(person.imageContent),
'imageContent should be an array');
404 assert.strictEqual(person.imageContent.length, 0,
'imageContent should be empty array');
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);
423 const selectedIds =
new Set<number>();
424 const iterations = 20;
426 for (let i = 0; i < iterations; i++) {
427 const person = await darling.getRandomPerson();
428 selectedIds.add(person.id);
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`);
436 assert.ok(selectedIds.size >= 1,
'Should select at least one character');
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);
447 const iterations = 10;
448 for (let i = 0; i < iterations; i++) {
449 const person = await darling.getRandomPerson();
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');
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);
468 const person1 = await darling.getRandomPerson();
469 const person2 = await darling.getRandomPerson();
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');
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);
492 await darling.getRandomPerson();
493 assert.fail(
'Should throw error for empty character array');
495 assert.ok(error instanceof Error,
'Should throw Error instance');
496 assert.ok(error.message.length > 0,
'Error should have meaningful message');
504 test(
'should throw error for invalid JSON format', async () => {
505 await
fs.writeFile(testFilePath,
'{ invalid json }');
506 const darling =
new Darling(testFilePath, tempDir);
509 await darling.getRandomPerson();
510 assert.fail(
'Should throw error for invalid JSON');
512 assert.ok(error instanceof Error,
'Should throw Error instance for invalid JSON');
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);
525 await darling.getRandomPerson();
526 assert.fail(
'Should throw error for non-array JSON');
528 assert.ok(error instanceof Error,
'Should throw Error instance for non-array JSON');
536 test(
'should handle missing optional properties', async () => {
537 const minimalCharacter = [{
539 name:
"Minimal Character",
543 description:
"Test description",
547 more_information:
"https://example.com",
552 await
fs.writeFile(testFilePath, JSON.stringify(minimalCharacter));
553 const darling =
new Darling(testFilePath, tempDir);
555 const person = await darling.getRandomPerson();
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');
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);
579 let capturedHtml =
'';
580 vscode.window.createWebviewPanel = (viewType:
string, title:
string, showOptions: any, options?: any) => {
581 const webviewMock = {
583 set html(value:
string) { this._html = value; capturedHtml = value; },
584 get html() {
return this._html; },
585 onDidReceiveMessage: () => ({ dispose: () => { } })
592 await darling.displayRandomPersonInWindow();
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');
606 test(
'should include character information in HTML', async () => {
607 await
fs.writeFile(testFilePath, JSON.stringify([mockCharacters[0]]));
608 const darling =
new Darling(testFilePath, tempDir);
610 await darling.displayRandomPersonInWindow();
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');
623 test(
'should include interactive buttons in HTML', async () => {
624 await
fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
625 const darling =
new Darling(testFilePath, tempDir);
627 await darling.displayRandomPersonInWindow();
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');
640 test(
'should include CSS styling', async () => {
641 await
fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
642 const darling =
new Darling(testFilePath, tempDir);
644 await darling.displayRandomPersonInWindow();
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');
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);
661 await darling.displayRandomPersonInWindow();
663 assert.ok(capturedHtml.includes(
'None'),
'Should display "None" for null alias');
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);
682 await darling.displayRandomPersonInWindow();
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');
694 test(
'should include zoom functionality script', async () => {
695 await
fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
696 const darling =
new Darling(testFilePath, tempDir);
698 await darling.displayRandomPersonInWindow();
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');
711 test(
'should include VS Code API integration', async () => {
712 await
fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
713 const darling =
new Darling(testFilePath, tempDir);
715 await darling.displayRandomPersonInWindow();
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');
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);
738 let messageHandlerCalled =
false;
740 vscode.window.createWebviewPanel = (viewType:
string, title:
string, showOptions: any, options?: any) => {
742 webview.onDidReceiveMessage = (handler: (message: any) => void) => {
743 messageHandlerCalled =
true;
746 handler({
type:
'copied' });
747 handler({
type:
'unknown' });
751 return { dispose: () => { } };
753 return { webview } as any;
756 await darling.displayRandomPersonInWindow();
759 assert.ok(messageHandlerCalled,
'Should setup message handler');
766 test(
'should create webview with correct parameters', async () => {
767 await
fs.writeFile(testFilePath, JSON.stringify([mockCharacters[0]]));
768 const darling =
new Darling(testFilePath, tempDir);
770 let capturedTitle =
'';
771 let capturedOptions: any =
null;
773 vscode.window.createWebviewPanel = (viewType:
string, title:
string, showOptions: any, options?: any) => {
774 capturedTitle = title;
775 capturedOptions = options;
781 await darling.displayRandomPersonInWindow();
783 assert.strictEqual(capturedTitle,
'Zero Two',
'Should use character name as panel title');
784 assert.ok(capturedOptions?.enableScripts,
'Should enable scripts in webview options');
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);
803 const startTime = Date.now();
804 const iterations = 100;
806 for (let i = 0; i < iterations; i++) {
807 const person = await darling.getRandomPerson();
808 assert.ok(person.id,
'Should return valid person');
811 const endTime = Date.now();
812 const duration = endTime - startTime;
815 assert.ok(duration < 5000, `Should complete ${iterations} selections in reasonable time (took ${duration}ms)`);
822 test(
'should reuse file loader instance efficiently', async () => {
823 await
fs.writeFile(testFilePath, JSON.stringify(mockCharacters));
824 const darling =
new Darling(testFilePath, tempDir);
827 const person1 = await darling.getRandomPerson();
828 const person2 = await darling.getRandomPerson();
829 const person3 = await darling.getRandomPerson();
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');
841 test(
'should handle large character datasets', async () => {
844 for (let i = 1; i <= 1000; 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:
861 await
fs.writeFile(testFilePath, JSON.stringify(largeDataset));
862 const darling =
new Darling(testFilePath, tempDir);
864 const startTime = Date.now();
865 const person = await darling.getRandomPerson();
866 const endTime = Date.now();
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');
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 assert from assert
Interface for test character data matching the JSON structure.
Represents a character from the Darling in the FranXX series.
export const Record< string,(...args:any[])=> string