Files
zellyy-finance/scripts/test-runner.cjs
hansoo 8343b25439 feat: Stage 2 TypeScript 타입 안전성 개선 - any 타입 83개 → 62개 대폭 감소
 주요 개선사항:
- any 타입 83개에서 62개로 21개 수정 (25% 감소)
- 모든 ESLint 에러 11개 → 0개 완전 해결
- 타입 안전성 대폭 향상으로 런타임 오류 가능성 감소

🔧 수정된 파일들:
• PWADebug.tsx - 사용하지 않는 import들에 _ prefix 추가
• categoryUtils.ts - 불필요한 any 캐스트 제거
• TransactionsHeader.tsx - BudgetData 인터페이스 정의
• storageUtils.ts - generic 타입과 unknown 타입 적용
• 각종 error handler들 - Error | {message?: string} 타입 적용
• test 파일들 - 적절한 mock 인터페이스 정의
• 유틸리티 파일들 - any → unknown 또는 적절한 타입으로 교체

🏆 성과:
- 코드 품질 크게 향상 (280 → 80 문제로 71% 감소)
- TypeScript 컴파일러의 타입 체크 효과성 증대
- 개발자 경험 개선 (IDE 자동완성, 타입 추론 등)

🧹 추가 정리:
- ESLint no-console/no-alert 경고 해결
- Prettier 포맷팅 적용으로 코드 스타일 통일

🎯 다음 단계: 남은 62개 any 타입 계속 개선 예정

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-14 10:08:51 +09:00

264 lines
6.7 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
/**
* 포괄적인 테스트 실행 스크립트
* CI/CD에서 사용하기 위한 테스트 스위트
*/
const projectRoot = path.join(__dirname, "..");
// 테스트 설정
const testConfig = {
unit: {
command: "npm run test:run",
name: "Unit Tests",
required: true,
},
type: {
command: "npm run type-check",
name: "Type Checking",
required: true,
},
lint: {
command: "npm run lint",
name: "ESLint",
required: true,
},
e2e: {
command: "npx playwright test",
name: "E2E Tests",
required: false,
},
build: {
command: "npm run build:prod",
name: "Build Test",
required: true,
},
};
// 명령행 인수 파싱
const args = process.argv.slice(2);
const options = {
failFast: args.includes("--fail-fast"),
skipOptional: args.includes("--skip-optional"),
environment:
args.find((arg) => arg.startsWith("--env="))?.split("=")[1] || "test",
coverage: args.includes("--coverage"),
verbose: args.includes("--verbose"),
};
console.log("🧪 Starting comprehensive test suite...\n");
console.log(`Environment: ${options.environment}`);
console.log(`Options: ${JSON.stringify(options, null, 2)}\n`);
const results = {
passed: [],
failed: [],
skipped: [],
startTime: Date.now(),
};
// 테스트 실행 함수
function runTest(testName, config) {
const startTime = Date.now();
console.log(`\n🔍 Running ${config.name}...`);
if (!config.required && options.skipOptional) {
console.log(`⏭️ Skipping optional test: ${config.name}`);
results.skipped.push(testName);
return true;
}
try {
const output = execSync(config.command, {
cwd: projectRoot,
encoding: "utf8",
stdio: options.verbose ? "inherit" : "pipe",
env: {
...process.env,
NODE_ENV: options.environment,
CI: "true",
},
});
const duration = Date.now() - startTime;
console.log(`${config.name} passed (${duration}ms)`);
if (options.verbose && output) {
console.log("Output:", output.slice(-500)); // 마지막 500자만 출력
}
results.passed.push({
name: testName,
duration,
config,
});
return true;
} catch (error) {
const duration = Date.now() - startTime;
console.log(`${config.name} failed (${duration}ms)`);
console.log("Error:", error.message);
if (error.stdout) {
console.log("STDOUT:", error.stdout.slice(-1000));
}
if (error.stderr) {
console.log("STDERR:", error.stderr.slice(-1000));
}
results.failed.push({
name: testName,
duration,
config,
error: error.message,
});
return false;
}
}
// 메인 테스트 실행
async function runAllTests() {
console.log("🚀 Test execution started...\n");
// 환경 설정
if (options.environment !== "test") {
console.log(`🔧 Setting up ${options.environment} environment...`);
try {
execSync(`node scripts/build-env.cjs ${options.environment}`, {
cwd: projectRoot,
stdio: "inherit",
});
} catch (error) {
console.log("⚠️ Environment setup failed, continuing with defaults");
}
}
// 테스트 순서 정의
const testOrder = ["lint", "type", "unit", "build", "e2e"];
for (const testName of testOrder) {
const config = testConfig[testName];
if (!config) continue;
const success = runTest(testName, config);
if (!success && config.required && options.failFast) {
console.log("\n💥 Fail-fast mode: Stopping on first failure");
break;
}
}
// 커버리지 실행 (요청된 경우)
if (options.coverage && !results.failed.length) {
console.log("\n📊 Running coverage analysis...");
try {
execSync("npm run test:coverage", {
cwd: projectRoot,
stdio: "inherit",
});
} catch (error) {
console.log("⚠️ Coverage analysis failed");
}
}
// 결과 요약
printResults();
// 종료 코드 결정
const hasRequiredFailures = results.failed.some(
(failure) => testConfig[failure.name]?.required
);
if (hasRequiredFailures) {
process.exit(1);
}
}
// 결과 출력
function printResults() {
const totalTime = Date.now() - results.startTime;
console.log("\n" + "=".repeat(60));
console.log("📋 TEST RESULTS SUMMARY");
console.log("=".repeat(60));
console.log(`\n⏱️ Total execution time: ${totalTime}ms`);
console.log(`✅ Passed: ${results.passed.length}`);
console.log(`❌ Failed: ${results.failed.length}`);
console.log(`⏭️ Skipped: ${results.skipped.length}`);
if (results.passed.length > 0) {
console.log("\n✅ PASSED TESTS:");
results.passed.forEach((test) => {
console.log(`${test.config.name} (${test.duration}ms)`);
});
}
if (results.failed.length > 0) {
console.log("\n❌ FAILED TESTS:");
results.failed.forEach((test) => {
const required = test.config.required ? "[REQUIRED]" : "[OPTIONAL]";
console.log(`${test.config.name} ${required} (${test.duration}ms)`);
console.log(` Error: ${test.error.slice(0, 100)}...`);
});
}
if (results.skipped.length > 0) {
console.log("\n⏭ SKIPPED TESTS:");
results.skipped.forEach((testName) => {
console.log(`${testConfig[testName].name}`);
});
}
// 상태 결정
const hasRequiredFailures = results.failed.some(
(failure) => testConfig[failure.name]?.required
);
if (hasRequiredFailures) {
console.log("\n🚨 OVERALL STATUS: FAILED");
console.log("Required tests failed. Build should not proceed.");
} else if (results.failed.length > 0) {
console.log("\n⚠ OVERALL STATUS: PASSED WITH WARNINGS");
console.log("Optional tests failed, but required tests passed.");
} else {
console.log("\n🎉 OVERALL STATUS: PASSED");
console.log("All tests passed successfully!");
}
// 결과를 JSON 파일로 저장 (CI에서 활용 가능)
const reportPath = path.join(projectRoot, "test-results.json");
const report = {
timestamp: new Date().toISOString(),
environment: options.environment,
options,
results,
summary: {
passed: results.passed.length,
failed: results.failed.length,
skipped: results.skipped.length,
totalTime,
status: hasRequiredFailures
? "FAILED"
: results.failed.length > 0
? "WARNING"
: "PASSED",
},
};
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
console.log(`\n📄 Detailed report saved to: test-results.json`);
}
// 스크립트 실행
runAllTests().catch((error) => {
console.error("💥 Test runner crashed:", error);
process.exit(1);
});