UNPKG

@zerospacegg/vynthra

Version:
265 lines (219 loc) โ€ข 6.65 kB
#!/usr/bin/env node /** * Test runner script for Vynthra Discord Bot Library * * This script runs the test suite and provides detailed output for CI/CD environments. * It can be used locally for development or in automated pipelines. */ import { spawn } from 'child_process'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const projectRoot = join(__dirname, '..'); interface TestRunOptions { watch?: boolean; coverage?: boolean; ui?: boolean; reporter?: string; silent?: boolean; filter?: string; } class TestRunner { private options: TestRunOptions; constructor(options: TestRunOptions = {}) { this.options = options; } async run(): Promise<number> { const args = this.buildVitestArgs(); if (!this.options.silent) { console.log('๐Ÿงช Running Vynthra Bot Library Tests...'); console.log(`๐Ÿ“ Project root: ${projectRoot}`); console.log(`๐Ÿ”ง Command: npx vitest ${args.join(' ')}`); console.log(''); } return new Promise((resolve) => { const child = spawn('npx', ['vitest', ...args], { cwd: projectRoot, stdio: 'inherit', env: { ...process.env, NODE_ENV: 'test', FORCE_COLOR: '1', // Enable colors in CI }, }); child.on('close', (code) => { const exitCode = code ?? 0; if (!this.options.silent) { if (exitCode === 0) { console.log('\nโœ… All tests passed!'); } else { console.log(`\nโŒ Tests failed with exit code: ${exitCode}`); } } resolve(exitCode); }); child.on('error', (error) => { console.error('โŒ Failed to start test runner:', error); resolve(1); }); // Handle process termination gracefully process.on('SIGINT', () => { child.kill('SIGINT'); }); process.on('SIGTERM', () => { child.kill('SIGTERM'); }); }); } private buildVitestArgs(): string[] { const args: string[] = []; if (this.options.watch) { args.push('--watch'); } else { args.push('run'); } if (this.options.coverage) { args.push('--coverage'); } if (this.options.ui) { args.push('--ui'); } if (this.options.reporter) { args.push('--reporter', this.options.reporter); } if (this.options.filter) { args.push('--grep', this.options.filter); } // Add default options for better CI/CD output if (process.env.CI) { args.push('--reporter=verbose'); args.push('--no-coverage.clean'); } return args; } } // Parse command line arguments function parseArgs(): TestRunOptions { const args = process.argv.slice(2); const options: TestRunOptions = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; switch (arg) { case '--watch': case '-w': options.watch = true; break; case '--coverage': case '-c': options.coverage = true; break; case '--ui': options.ui = true; break; case '--reporter': case '-r': options.reporter = args[++i]; break; case '--silent': case '-s': options.silent = true; break; case '--filter': case '-f': options.filter = args[++i]; break; case '--help': case '-h': printHelp(); process.exit(0); break; default: if (arg.startsWith('--filter=')) { options.filter = arg.split('=')[1]; } else if (arg.startsWith('--reporter=')) { options.reporter = arg.split('=')[1]; } else { console.error(`โŒ Unknown argument: ${arg}`); printHelp(); process.exit(1); } } } return options; } function printHelp(): void { console.log(` ๐Ÿงช Vynthra Bot Library Test Runner Usage: node test/run-tests.ts [options] Options: --watch, -w Run tests in watch mode --coverage, -c Run tests with coverage report --ui Open Vitest UI --reporter, -r <name> Specify test reporter (default, verbose, json, etc.) --filter, -f <pattern> Filter tests by pattern --silent, -s Run silently (minimal output) --help, -h Show this help message Examples: node test/run-tests.ts # Run all tests once node test/run-tests.ts --watch # Run tests in watch mode node test/run-tests.ts --coverage # Run tests with coverage node test/run-tests.ts --ui # Open test UI node test/run-tests.ts --filter="config" # Run only config tests node test/run-tests.ts --reporter=json # Output results as JSON Environment Variables: CI=true # Automatically detected, enables CI-friendly output NODE_ENV=test # Automatically set during test runs `); } // Predefined test suites const TEST_SUITES = { unit: 'test/bot/*.test.ts', integration: 'test/bot/integration.test.ts', utils: 'test/bot/utils.test.ts', client: 'test/bot/client.test.ts', config: 'test/bot/config.test.ts', deploy: 'test/bot/deploy.test.ts', subcommands: 'test/bot/subcommands.test.ts', }; // Add suite shortcuts function parseSuiteArgs(): TestRunOptions { const args = process.argv.slice(2); const options = parseArgs(); // Check for suite shortcuts for (const arg of args) { if (arg.startsWith('--suite=')) { const suite = arg.split('=')[1]; if (suite in TEST_SUITES) { options.filter = TEST_SUITES[suite as keyof typeof TEST_SUITES]; console.log(`๐Ÿ“ฆ Running test suite: ${suite}`); console.log(`๐ŸŽฏ Filter: ${options.filter}`); } else { console.error(`โŒ Unknown test suite: ${suite}`); console.log('Available suites:', Object.keys(TEST_SUITES).join(', ')); process.exit(1); } } } return options; } // Main execution async function main(): Promise<void> { try { const options = parseSuiteArgs(); const runner = new TestRunner(options); const exitCode = await runner.run(); process.exit(exitCode); } catch (error) { console.error('โŒ Test runner failed:', error); process.exit(1); } } // Run if this file is executed directly if (import.meta.url === `file://${process.argv[1]}`) { main().catch((error) => { console.error('โŒ Unhandled error:', error); process.exit(1); }); } export { TestRunner, parseArgs, TEST_SUITES };