34import * as fs from
'fs/promises';
35import * as
path from
'path';
36import * as
os from
'os';
37import { RandomLogo,
logo } from
'../modules/randomLogo';
44 private _html:
string =
'';
45 private messageHandlers: Array<(message: any) =>
void> = [];
59 set html(value:
string) {
69 this.messageHandlers.push(handler);
70 return { dispose: () => { } };
78 this.messageHandlers.forEach(handler => handler(message));
103 public viewType:
string,
104 public title:
string,
105 public showOptions:
vscode.ViewColumn,
106 public options:
vscode.WebviewPanelOptions &
vscode.WebviewOptions
116suite(
'RandomLogo Test Suite', () => {
120 let originalCreateWebviewPanel: typeof
vscode.window.createWebviewPanel;
131 tempDir = await fs.mkdtemp(
path.join(
os.tmpdir(),
'randomlogo-test-'));
132 logoDir =
path.join(tempDir,
'logos');
133 subDir =
path.join(logoDir,
'subdir');
135 await fs.mkdir(logoDir, { recursive:
true });
136 await fs.mkdir(subDir, { recursive:
true });
141 name:
'simple-logo.txt',
142 content:
' **** \n * * \n * * \n **** '
145 name:
'complex-logo.txt',
146 content:
'╔══════════════════════════════════════╗\n║ LOGO TITLE ║\n║ ║\n║ ██████ ██████ ███ ███ ██████ ║\n║ ██ ██ ██ ████ ████ ██ ██ ║\n║ ██ ██ ██ ██ ████ ██ ██████ ║\n║ ██ ██ ██ ██ ██ ██ ██ ║\n║ ██████ ██████ ██ ██ ██ ║\n║ ║\n╚══════════════════════════════════════╝'
149 name:
'multiline-logo.txt',
150 content:
'Line 1\nLine 2\nLine 3\nLine 4\nLine 5'
154 const subDirLogoFiles = [
156 name:
'nested-logo.txt',
157 content:
' /\\_/\\ \n ( o.o ) \n > ^ < '
162 for (
const file of logoFiles) {
163 await fs.writeFile(
path.join(logoDir, file.name), file.content,
'utf8');
167 for (
const file of subDirLogoFiles) {
168 await fs.writeFile(
path.join(subDir, file.name), file.content,
'utf8');
172 await fs.writeFile(
path.join(logoDir,
'readme.md'),
'# This is not a logo',
'utf8');
173 await fs.writeFile(
path.join(logoDir,
'config.json'),
'{"test": true}',
'utf8');
176 originalCreateWebviewPanel =
vscode.window.createWebviewPanel;
177 vscode.window.createWebviewPanel = (
180 showOptions:
vscode.ViewColumn | { readonly viewColumn:
vscode.ViewColumn; readonly preserveFocus?:
boolean | undefined; },
181 options?:
vscode.WebviewPanelOptions &
vscode.WebviewOptions
183 const column = typeof showOptions ===
'object' ? showOptions.viewColumn : showOptions;
184 const opts = options || {};
186 lastCreatedWebview = lastCreatedPanel.webview;
190 Object.defineProperty(lastCreatedWebview,
'html', {
191 get: () => capturedHtml,
193 capturedHtml = value;
197 return lastCreatedPanel as any;
205 teardown(async () => {
207 if (originalCreateWebviewPanel) {
208 vscode.window.createWebviewPanel = originalCreateWebviewPanel;
213 await fs.rm(tempDir, { recursive:
true, force:
true });
223 suite(
'Constructor and Initialization', () => {
228 test(
'should create instance with default parameters', () => {
237 test(
'should create instance with root directory parameter', async () => {
242 await
new Promise(resolve => setTimeout(resolve, 50));
249 test(
'should create instance with both root directory and working directory', async () => {
250 const randomLogo =
new RandomLogo(logoDir, tempDir);
254 await
new Promise(resolve => setTimeout(resolve, 50));
261 test(
'should handle undefined parameters gracefully', () => {
262 const randomLogo =
new RandomLogo(undefined, undefined);
271 suite(
'Directory and Path Management', () => {
276 test(
'should update root directory successfully', async () => {
278 const result = await
randomLogo.updateRootDir(logoDir);
279 assert.strictEqual(result,
true);
286 test(
'should handle invalid root directory gracefully', async () => {
288 const invalidPath =
path.join(tempDir,
'nonexistent');
289 const result = await
randomLogo.updateRootDir(invalidPath);
290 assert.strictEqual(result,
false);
297 test(
'should update current working directory successfully', () => {
299 const result =
randomLogo.updateCurrentWorkingDirectory(tempDir);
300 assert.strictEqual(result,
true);
307 test(
'should handle relative paths with working directory', () => {
309 randomLogo.updateCurrentWorkingDirectory(tempDir);
310 assert.strictEqual(
randomLogo.updateCurrentWorkingDirectory(
'./test'),
true);
318 suite(
'Logo File Discovery and Management', () => {
323 test(
'should discover logo files in root directory', async () => {
327 await
new Promise(resolve => setTimeout(resolve, 100));
336 assert.fail(
'Should have discovered logo files');
344 test(
'should discover logo files recursively in subdirectories', async () => {
348 await
new Promise(resolve => setTimeout(resolve, 100));
351 const logoFileNames:
string[] = [];
352 for (let i = 0; i < 10; i++) {
355 logoFileNames.push(
logo.fileName);
362 assert.ok(logoFileNames.length > 0,
'Should have found logo files');
363 assert.ok(logoFileNames.some(name => name.endsWith(
'.txt')),
'Should have .txt files');
370 test(
'should filter non-txt files correctly', async () => {
374 await
new Promise(resolve => setTimeout(resolve, 100));
377 for (let i = 0; i < 5; i++) {
380 assert.ok(
logo.fileName.endsWith(
'.txt'), `Logo file ${logo.fileName} should be a .txt file`);
391 test(
'should handle empty directories gracefully', async () => {
392 const emptyDir =
path.join(tempDir,
'empty');
393 await fs.mkdir(emptyDir, { recursive:
true });
398 await
new Promise(resolve => setTimeout(resolve, 50));
402 assert.fail(
'Should throw error for empty directory');
404 assert.ok(error instanceof Error);
413 suite(
'Random Selection Algorithm', () => {
418 test(
'should select different logos on multiple calls', async () => {
422 await
new Promise(resolve => setTimeout(resolve, 100));
424 const selectedLogos =
new Set<string>();
427 for (let i = 0; i < 10; i++) {
430 selectedLogos.add(
logo.fileName);
437 assert.ok(selectedLogos.size > 0,
'Should select at least one logo');
444 test(
'should always return valid logo from dataset', async () => {
448 await
new Promise(resolve => setTimeout(resolve, 100));
450 for (let i = 0; i < 5; i++) {
454 assert.ok(
logo.fileName,
'Should have a filename');
455 assert.ok(
logo.fileName.endsWith(
'.txt'),
'Should be a .txt file');
456 assert.ok(Array.isArray(
logo.logoContent),
'Logo content should be an array');
457 assert.ok(
logo.logoContent.length > 0,
'Logo should have content lines');
468 test(
'should handle single logo file correctly', async () => {
470 const singleLogoDir =
path.join(tempDir,
'single');
471 await fs.mkdir(singleLogoDir, { recursive:
true });
472 await fs.writeFile(
path.join(singleLogoDir,
'only-logo.txt'),
'Single\nLogo\nContent',
'utf8');
474 const randomLogo =
new RandomLogo(singleLogoDir);
477 await
new Promise(resolve => setTimeout(resolve, 50));
481 assert.strictEqual(
logo.fileName,
'only-logo.txt');
482 assert.deepStrictEqual(
logo.logoContent, [
'Single',
'Logo',
'Content']);
489 test(
'should properly split logo content by lines', async () => {
493 await
new Promise(resolve => setTimeout(resolve, 100));
497 assert.ok(Array.isArray(
logo.logoContent),
'Logo content should be an array of lines');
500 const joinedContent =
logo.logoContent.join(
'\n');
501 assert.ok(joinedContent.length > 0,
'Content should not be empty');
509 suite(
'Error Handling and Edge Cases', () => {
514 test(
'should throw error when no root directory is set', async () => {
519 assert.fail(
'Should throw error when no root directory is set');
521 assert.ok(error instanceof Error);
529 test(
'should handle corrupted or empty logo files gracefully', async () => {
531 const emptyLogoDir =
path.join(tempDir,
'empty-files');
532 await fs.mkdir(emptyLogoDir, { recursive:
true });
533 await fs.writeFile(
path.join(emptyLogoDir,
'empty-logo.txt'),
'',
'utf8');
535 const randomLogo =
new RandomLogo(emptyLogoDir);
538 await
new Promise(resolve => setTimeout(resolve, 50));
542 assert.strictEqual(
logo.fileName,
'empty-logo.txt');
545 assert.strictEqual(
logo.logoContent.length, 1);
553 test(
'should handle permission errors during file discovery', async () => {
557 const result = await
randomLogo.updateRootDir(
'/nonexistent/path/that/should/not/exist');
558 assert.strictEqual(result,
false);
565 test(
'should handle various line ending formats', async () => {
567 const lineEndingDir =
path.join(tempDir,
'line-endings');
568 await fs.mkdir(lineEndingDir, { recursive:
true });
570 await fs.writeFile(
path.join(lineEndingDir,
'unix-endings.txt'),
'Line1\nLine2\nLine3',
'utf8');
571 await fs.writeFile(
path.join(lineEndingDir,
'windows-endings.txt'),
'Line1\r\nLine2\r\nLine3',
'utf8');
573 const randomLogo =
new RandomLogo(lineEndingDir);
576 await
new Promise(resolve => setTimeout(resolve, 50));
578 for (let i = 0; i < 5; i++) {
582 assert.strictEqual(
logo.logoContent.length, 3);
583 assert.strictEqual(
logo.logoContent[0],
'Line1');
584 assert.strictEqual(
logo.logoContent[1],
'Line2');
585 assert.strictEqual(
logo.logoContent[2],
'Line3');
594 suite(
'HTML Content Generation', () => {
599 test(
'should generate valid HTML content for webview', async () => {
603 await
new Promise(resolve => setTimeout(resolve, 100));
607 assert.ok(capturedHtml,
'Should generate HTML content');
608 assert.ok(capturedHtml.includes(
'<!DOCTYPE html>'),
'Should be valid HTML');
609 assert.ok(capturedHtml.includes(
'<html lang="en">'),
'Should have language attribute');
610 assert.ok(capturedHtml.includes(
'<body>'),
'Should have body tag');
617 test(
'should include logo content in HTML', async () => {
621 await
new Promise(resolve => setTimeout(resolve, 100));
625 assert.ok(capturedHtml.includes(
'<pre id="ascii">'),
'Should include ASCII art container');
627 const preMatch = capturedHtml.match(/<pre
id=
"ascii">(.*?)<\/pre>/s);
628 assert.ok(preMatch,
'Should have ASCII art content');
629 assert.ok(preMatch[1].length > 0,
'ASCII art content should not be empty');
636 test(
'should include interactive buttons in HTML', async () => {
640 await
new Promise(resolve => setTimeout(resolve, 100));
644 assert.ok(capturedHtml.includes(
'id="copyBtn"'),
'Should include copy button');
645 assert.ok(capturedHtml.includes(
'id="zoomInBtn"'),
'Should include zoom in button');
646 assert.ok(capturedHtml.includes(
'id="zoomOutBtn"'),
'Should include zoom out button');
653 test(
'should include CSS styling', async () => {
657 await
new Promise(resolve => setTimeout(resolve, 100));
661 assert.ok(capturedHtml.includes(
'<style>'),
'Should include CSS styling');
662 assert.ok(capturedHtml.includes(
'font-family:'),
'Should have font family styling');
663 assert.ok(capturedHtml.includes(
'pre {'),
'Should have pre tag styling for ASCII art');
670 test(
'should include logo filename in display', async () => {
674 await
new Promise(resolve => setTimeout(resolve, 100));
680 capturedHtml.includes(
'.txt') ||
681 lastCreatedPanel.title.includes(
'.txt'),
682 'Should display logo filename'
690 test(
'should handle empty or undefined logo content gracefully', async () => {
692 const problematicDir =
path.join(tempDir,
'problematic');
693 await fs.mkdir(problematicDir, { recursive:
true });
694 await fs.writeFile(
path.join(problematicDir,
'empty.txt'),
'',
'utf8');
696 const randomLogo =
new RandomLogo(problematicDir);
699 await
new Promise(resolve => setTimeout(resolve, 50));
704 assert.ok(capturedHtml,
'Should generate HTML even with empty content');
705 assert.ok(capturedHtml.includes(
'<!DOCTYPE html>'),
'Should be valid HTML');
713 suite(
'JavaScript Functionality', () => {
718 test(
'should include copy button script', async () => {
722 await
new Promise(resolve => setTimeout(resolve, 100));
726 assert.ok(capturedHtml.includes(
"getElementById('copyBtn')"),
'Should include copy button script');
727 assert.ok(capturedHtml.includes(
'navigator.clipboard.writeText'),
'Should use clipboard API');
728 assert.ok(capturedHtml.includes(
"vscode.postMessage({ type: 'copied' })"),
'Should post message to VS Code');
735 test(
'should include zoom functionality script', async () => {
739 await
new Promise(resolve => setTimeout(resolve, 100));
743 assert.ok(capturedHtml.includes(
'updateFontSize'),
'Should include font size update function');
744 assert.ok(capturedHtml.includes(
"getElementById('zoomInBtn')"),
'Should include zoom in handler');
745 assert.ok(capturedHtml.includes(
"getElementById('zoomOutBtn')"),
'Should include zoom out handler');
746 assert.ok(capturedHtml.includes(
'currentSize'),
'Should track current font size');
753 test(
'should include VS Code API integration', async () => {
757 await
new Promise(resolve => setTimeout(resolve, 100));
761 assert.ok(capturedHtml.includes(
'acquireVsCodeApi()'),
'Should acquire VS Code API');
762 assert.ok(capturedHtml.includes(
'vscode.postMessage'),
'Should use VS Code message posting');
769 test(
'should implement font size constraints in zoom', async () => {
773 await
new Promise(resolve => setTimeout(resolve, 100));
778 assert.ok(capturedHtml.includes(
'>= 2'),
'Should have minimum font size constraint');
780 assert.ok(capturedHtml.includes(
'currentSize = 20'),
'Should initialize font size');
788 suite(
'Integration and Message Handling', () => {
793 test(
'should setup message handler for copy events', async () => {
797 await
new Promise(resolve => setTimeout(resolve, 100));
799 let messageReceived =
false;
800 let receivedMessage: any =
null;
803 const originalOnMessage = lastCreatedWebview?.onDidReceiveMessage;
804 if (lastCreatedWebview) {
805 lastCreatedWebview.onDidReceiveMessage = (handler: (message: any) => void) => {
817 assert.ok(lastCreatedPanel,
'Should create webview panel');
818 assert.ok(lastCreatedWebview,
'Should create webview');
825 test(
'should create webview with correct parameters', async () => {
829 await
new Promise(resolve => setTimeout(resolve, 100));
833 assert.ok(lastCreatedPanel,
'Should create webview panel');
834 assert.strictEqual(lastCreatedPanel.showOptions,
vscode.ViewColumn.One,
'Should use ViewColumn.One');
835 assert.ok(lastCreatedPanel.options.enableScripts,
'Should enable scripts');
836 assert.ok(lastCreatedPanel.title.endsWith(
'.txt'),
'Title should be the filename');
843 test(
'should handle message communication properly', async () => {
847 await
new Promise(resolve => setTimeout(resolve, 100));
852 if (lastCreatedWebview && lastCreatedWebview.postMessage) {
853 lastCreatedWebview.postMessage({
type:
'copied' });
857 assert.ok(
true,
'Message handling should not throw errors');
865 suite(
'Performance and Memory Management', () => {
870 test(
'should handle multiple rapid logo selections', async () => {
874 await
new Promise(resolve => setTimeout(resolve, 100));
876 const startTime = Date.now();
879 for (let i = 0; i < 10; i++) {
887 const endTime = Date.now();
888 const duration = endTime - startTime;
891 assert.ok(duration < 5000,
'Multiple selections should be reasonably fast');
898 test(
'should reuse file loader instances efficiently', async () => {
902 await
new Promise(resolve => setTimeout(resolve, 100));
905 const logo1 = await
randomLogo.getRandomLogoFromFolder();
906 const logo2 = await
randomLogo.getRandomLogoFromFolder();
908 assert.ok(logo1.fileName.endsWith(
'.txt'));
909 assert.ok(logo2.fileName.endsWith(
'.txt'));
912 assert.ok(Array.isArray(logo1.logoContent));
913 assert.ok(Array.isArray(logo2.logoContent));
920 test(
'should handle large numbers of logo files', async () => {
922 const manyLogosDir =
path.join(tempDir,
'many-logos');
923 await fs.mkdir(manyLogosDir, { recursive:
true });
926 for (let i = 0; i < 50; i++) {
927 const content = `Logo ${i}\n${
'*'.repeat(i % 10 + 1)}\nEnd`;
928 await fs.writeFile(
path.join(manyLogosDir, `
logo-${i}.txt`), content,
'utf8');
931 const randomLogo =
new RandomLogo(manyLogosDir);
934 await
new Promise(resolve => setTimeout(resolve, 200));
946 test(
'should handle directory updates without memory leaks', async () => {
950 await
new Promise(resolve => setTimeout(resolve, 100));
968 suite(
'File System Integration', () => {
973 test(
'should handle symbolic links appropriately', async () => {
976 const symlinkDir =
path.join(tempDir,
'symlink-test');
977 await fs.mkdir(symlinkDir, { recursive:
true });
980 const targetFile =
path.join(logoDir,
'simple-logo.txt');
981 const symlinkFile =
path.join(symlinkDir,
'symlinked-logo.txt');
983 await fs.symlink(targetFile, symlinkFile);
985 const randomLogo =
new RandomLogo(symlinkDir);
988 await
new Promise(resolve => setTimeout(resolve, 50));
995 console.log(
'Skipping symlink test - not supported on this system');
1003 test(
'should respect file system permissions', async () => {
1007 const result = await
randomLogo.updateRootDir(
'/etc');
1015 assert.ok(error instanceof Error);
1019 assert.ok(
true,
'Permission handling works correctly');
Mock implementation of VS Code webview panel for testing.
constructor(public viewType:string, public title:string, public showOptions:vscode.ViewColumn, public options:vscode.WebviewPanelOptions &vscode.WebviewOptions)
Constructs a new mock webview panel with specified configuration.
Mock implementation of VS Code webview for testing.
postMessage(message:any)
Posts a message to all registered handlers.
onDidReceiveMessage(handler:(message:any)=> void)
Registers a message handler for webview communication.
get html()
Gets the current HTML content of the webview.
export const randomLogo
Whether to use random logo selection instead of default logo.
export const export const string[]
Structure representing a loaded ASCII art logo with metadata.
import *as vscode from vscode
import *as path from path
import *as assert from assert
import *as vscode from vscode