@zerospacegg/iolin
Version:
Pure TypeScript implementation of ZeroSpace game data processing (PKL-free)
125 lines ⢠4.12 kB
JavaScript
/**
* Test that ensures all TypeScript files in src/zerospace/ export
* proper entity subclasses as their default export
*/
import { promises as fs } from 'fs';
import path from 'path';
// Base entity classes that are valid exports
const VALID_BASE_CLASSES = [
'GameEntity',
'GameMechanic',
'GameFactionEntity',
'GameUnit',
'GameBuilding',
'GameUpgrade',
'GameAbility',
'GameAttack',
'GamePassive',
'GameSpell',
'GameTurret',
// Co-op specific classes
'CoopCommanderUnit',
'CoopBuilding',
'CoopUnit',
// Mercenary classes
'MercenaryFaction',
'MercenaryUnit',
'MercenaryBuilding',
'MercenaryHero',
// Game modifier classes
'Boon',
'Mutator',
// Nonplayer classes
'NonPlayerFaction',
'NonPlayerUnit',
'NonPlayerBuilding'
];
/**
* Recursively find all .ts files in a directory
*/
async function findTsFiles(dir) {
const files = [];
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
const subFiles = await findTsFiles(fullPath);
files.push(...subFiles);
}
else if (entry.isFile() && entry.name.endsWith('.ts')) {
files.push(fullPath);
}
}
return files;
}
/**
* Check if a file exports a valid entity class
*/
async function isValidEntityFile(filePath) {
try {
const content = await fs.readFile(filePath, 'utf-8');
// Skip test files
if (filePath.includes('.test.') || filePath.includes('/test/')) {
return { valid: true, reason: 'test file - skipped' };
}
// Look for class declarations that extend valid base classes
const classRegex = /export\s+(?:default\s+)?class\s+\w+\s+extends\s+(\w+)/g;
const matches = [...content.matchAll(classRegex)];
if (matches.length === 0) {
return { valid: false, reason: 'no class extending entity base class found' };
}
// Check if any class extends a valid base class
for (const match of matches) {
const baseClass = match[1];
if (VALID_BASE_CLASSES.includes(baseClass)) {
return { valid: true, reason: `extends ${baseClass}` };
}
}
return { valid: false, reason: `classes found but none extend valid base classes: ${matches.map(m => m[1]).join(', ')}` };
}
catch (error) {
return { valid: false, reason: `error reading file: ${error instanceof Error ? error.message : String(error)}` };
}
}
/**
* Main test function
*/
async function testEntityExports() {
const zerostpaceSrcDir = path.resolve(process.cwd(), 'src/zerospace');
console.log('š Finding all TypeScript files in src/zerospace/...');
const tsFiles = await findTsFiles(zerostpaceSrcDir);
console.log(`š Found ${tsFiles.length} TypeScript files`);
const results = [];
for (const file of tsFiles) {
const relativePath = path.relative(zerostpaceSrcDir, file);
const result = await isValidEntityFile(file);
results.push({
file: relativePath,
valid: result.valid,
reason: result.reason || 'unknown'
});
if (!result.valid) {
console.log(`ā ${relativePath}: ${result.reason}`);
}
else {
console.log(`ā
${relativePath}: ${result.reason}`);
}
}
const invalidFiles = results.filter(r => !r.valid);
if (invalidFiles.length > 0) {
console.log(`\nš„ ${invalidFiles.length} files do not export valid entity classes:`);
invalidFiles.forEach(f => {
console.log(` - ${f.file}: ${f.reason}`);
});
process.exit(1);
}
else {
console.log(`\nš All ${results.length} files export valid entity classes!`);
}
}
// Run the test
testEntityExports().catch(error => {
console.error('Test failed:', error);
process.exit(1);
});
//# sourceMappingURL=entity-exports.test.js.map