@zerospacegg/vynthra
Version:
Discord bot for ZeroSpace.gg data
265 lines (219 loc) โข 6.65 kB
text/typescript
/**
* 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 };