🎨 format code

This commit is contained in:
Tw93
2025-08-14 11:20:15 +08:00
parent 7adeb91ae9
commit 256cdc77ee
12 changed files with 745 additions and 639 deletions

View File

@@ -66,6 +66,6 @@ jobs:
- Performance considerations for desktop app packaging - Performance considerations for desktop app packaging
- CLI usability and error handling - CLI usability and error handling
- Test coverage completeness - Test coverage completeness
Be constructive and helpful in your feedback. Be constructive and helpful in your feedback.
use_sticky_comment: true use_sticky_comment: true

View File

@@ -98,7 +98,7 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 22
cache: 'npm' cache: "npm"
- name: Install Rust (Ubuntu) - name: Install Rust (Ubuntu)
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
@@ -157,21 +157,21 @@ jobs:
run: | run: |
echo "# 🎯 Quality & Testing Summary" >> $GITHUB_STEP_SUMMARY echo "# 🎯 Quality & Testing Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.formatting.result }}" == "success" ]; then if [ "${{ needs.formatting.result }}" == "success" ]; then
echo "✅ **Code Formatting**: PASSED" >> $GITHUB_STEP_SUMMARY echo "✅ **Code Formatting**: PASSED" >> $GITHUB_STEP_SUMMARY
else else
echo "❌ **Code Formatting**: FAILED" >> $GITHUB_STEP_SUMMARY echo "❌ **Code Formatting**: FAILED" >> $GITHUB_STEP_SUMMARY
fi fi
if [ "${{ needs.rust-quality.result }}" == "success" ]; then if [ "${{ needs.rust-quality.result }}" == "success" ]; then
echo "✅ **Rust Quality**: PASSED" >> $GITHUB_STEP_SUMMARY echo "✅ **Rust Quality**: PASSED" >> $GITHUB_STEP_SUMMARY
else else
echo "❌ **Rust Quality**: FAILED" >> $GITHUB_STEP_SUMMARY echo "❌ **Rust Quality**: FAILED" >> $GITHUB_STEP_SUMMARY
fi fi
if [ "${{ needs.cli-tests.result }}" == "success" ]; then if [ "${{ needs.cli-tests.result }}" == "success" ]; then
echo "✅ **CLI Tests**: PASSED" >> $GITHUB_STEP_SUMMARY echo "✅ **CLI Tests**: PASSED" >> $GITHUB_STEP_SUMMARY
else else
echo "❌ **CLI Tests**: FAILED" >> $GITHUB_STEP_SUMMARY echo "❌ **CLI Tests**: FAILED" >> $GITHUB_STEP_SUMMARY
fi fi

View File

@@ -113,4 +113,4 @@ jobs:
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
platforms: linux/amd64 platforms: linux/amd64

View File

@@ -79,7 +79,7 @@ npm run build:mac # macOS universal build
- [ ] Run `npm test` for comprehensive validation (35 tests) - [ ] Run `npm test` for comprehensive validation (35 tests)
- [ ] Test on target platforms - [ ] Test on target platforms
- [ ] Verify injection system works - [ ] Verify injection system works
- [ ] Check system tray integration - [ ] Check system tray integration
- [ ] Validate window behavior - [ ] Validate window behavior
- [ ] Test with weekly.tw93.fun URL - [ ] Test with weekly.tw93.fun URL
- [ ] Verify remote icon functionality (https://gw.alipayobjects.com/os/k/fw/weekly.icns) - [ ] Verify remote icon functionality (https://gw.alipayobjects.com/os/k/fw/weekly.icns)

View File

@@ -48,7 +48,7 @@ npm run dev
# Run all tests (unit + integration + builder) # Run all tests (unit + integration + builder)
npm test npm test
# Build CLI for testing # Build CLI for testing
npm run cli:build npm run cli:build
``` ```

View File

@@ -54,7 +54,9 @@ export default abstract class BaseBuilder {
// For global CLI installation, always use npm // For global CLI installation, always use npm
const packageManager = 'npm'; const packageManager = 'npm';
const registryOption = isChina ? ' --registry=https://registry.npmmirror.com' : ''; const registryOption = isChina
? ' --registry=https://registry.npmmirror.com'
: '';
if (isChina) { if (isChina) {
logger.info('✺ Located in China, using npm/rsProxy CN mirror.'); logger.info('✺ Located in China, using npm/rsProxy CN mirror.');
@@ -130,5 +132,4 @@ export default abstract class BaseBuilder {
`${fileName}.${fileType}`, `${fileName}.${fileType}`,
); );
} }
} }

View File

@@ -154,7 +154,7 @@ runner.addTest(
// Test logic here // Test logic here
return true; // or false return true; // or false
}, },
"Test description" "Test description",
); );
``` ```
@@ -168,7 +168,7 @@ runner.addTest(
// Async test logic // Async test logic
return await someAsyncOperation(); return await someAsyncOperation();
}, },
TIMEOUTS.MEDIUM TIMEOUTS.MEDIUM,
); );
``` ```

View File

@@ -2,15 +2,15 @@
/** /**
* Builder-specific Tests for Pake CLI * Builder-specific Tests for Pake CLI
* *
* These tests verify platform-specific builder logic and file naming patterns * These tests verify platform-specific builder logic and file naming patterns
* Based on analysis of bin/builders/ implementation * Based on analysis of bin/builders/ implementation
*/ */
import { execSync } from 'child_process'; import { execSync } from "child_process";
import path from 'path'; import path from "path";
import config, { TEST_URLS, TEST_NAMES } from './test.config.js'; import config, { TEST_URLS, TEST_NAMES } from "./test.config.js";
import ora from 'ora'; import ora from "ora";
class BuilderTestRunner { class BuilderTestRunner {
constructor() { constructor() {
@@ -18,17 +18,17 @@ class BuilderTestRunner {
this.results = []; this.results = [];
} }
addTest(name, testFn, description = '') { addTest(name, testFn, description = "") {
this.tests.push({ name, testFn, description }); this.tests.push({ name, testFn, description });
} }
async runAll() { async runAll() {
console.log('🏗️ Builder-specific Tests'); console.log("🏗️ Builder-specific Tests");
console.log('==========================\n'); console.log("==========================\n");
for (const [index, test] of this.tests.entries()) { for (const [index, test] of this.tests.entries()) {
const spinner = ora(`Running ${test.name}...`).start(); const spinner = ora(`Running ${test.name}...`).start();
try { try {
const result = await test.testFn(); const result = await test.testFn();
if (result) { if (result) {
@@ -39,8 +39,14 @@ class BuilderTestRunner {
this.results.push({ name: test.name, passed: false }); this.results.push({ name: test.name, passed: false });
} }
} catch (error) { } catch (error) {
spinner.fail(`${index + 1}. ${test.name}: ERROR - ${error.message.slice(0, 50)}...`); spinner.fail(
this.results.push({ name: test.name, passed: false, error: error.message }); `${index + 1}. ${test.name}: ERROR - ${error.message.slice(0, 50)}...`,
);
this.results.push({
name: test.name,
passed: false,
error: error.message,
});
} }
} }
@@ -48,18 +54,22 @@ class BuilderTestRunner {
} }
displayResults() { displayResults() {
const passed = this.results.filter(r => r.passed).length; const passed = this.results.filter((r) => r.passed).length;
const total = this.results.length; const total = this.results.length;
console.log(`\n📊 Builder Test Results: ${passed}/${total} passed\n`); console.log(`\n📊 Builder Test Results: ${passed}/${total} passed\n`);
if (passed === total) { if (passed === total) {
console.log('🎉 All builder tests passed!'); console.log("🎉 All builder tests passed!");
} else { } else {
console.log('❌ Some builder tests failed'); console.log("❌ Some builder tests failed");
this.results.filter(r => !r.passed).forEach(result => { this.results
console.log(` - ${result.name}${result.error ? `: ${result.error}` : ''}`); .filter((r) => !r.passed)
}); .forEach((result) => {
console.log(
` - ${result.name}${result.error ? `: ${result.error}` : ""}`,
);
});
} }
} }
} }
@@ -68,116 +78,125 @@ const runner = new BuilderTestRunner();
// Platform-specific file naming tests // Platform-specific file naming tests
runner.addTest( runner.addTest(
'Mac Builder File Naming Pattern', "Mac Builder File Naming Pattern",
() => { () => {
try { try {
// Test macOS file naming: name_version_arch.dmg // Test macOS file naming: name_version_arch.dmg
const mockName = 'TestApp'; const mockName = "TestApp";
const mockVersion = '1.0.0'; const mockVersion = "1.0.0";
const arch = process.arch === 'arm64' ? 'aarch64' : process.arch; const arch = process.arch === "arm64" ? "aarch64" : process.arch;
// Expected pattern: TestApp_1.0.0_aarch64.dmg (for M1) or TestApp_1.0.0_x64.dmg (for Intel) // Expected pattern: TestApp_1.0.0_aarch64.dmg (for M1) or TestApp_1.0.0_x64.dmg (for Intel)
const expectedPattern = `${mockName}_${mockVersion}_${arch}`; const expectedPattern = `${mockName}_${mockVersion}_${arch}`;
const universalPattern = `${mockName}_${mockVersion}_universal`; const universalPattern = `${mockName}_${mockVersion}_universal`;
// Test that naming pattern is consistent // Test that naming pattern is consistent
return expectedPattern.includes(mockName) && return (
expectedPattern.includes(mockVersion) && expectedPattern.includes(mockName) &&
(expectedPattern.includes(arch) || universalPattern.includes('universal')); expectedPattern.includes(mockVersion) &&
(expectedPattern.includes(arch) ||
universalPattern.includes("universal"))
);
} catch (error) { } catch (error) {
return false; return false;
} }
}, },
'Should generate correct macOS file naming pattern' "Should generate correct macOS file naming pattern",
); );
runner.addTest( runner.addTest(
'Windows Builder File Naming Pattern', "Windows Builder File Naming Pattern",
() => { () => {
try { try {
// Test Windows file naming: name_version_arch_language.msi // Test Windows file naming: name_version_arch_language.msi
const mockName = 'TestApp'; const mockName = "TestApp";
const mockVersion = '1.0.0'; const mockVersion = "1.0.0";
const arch = process.arch; const arch = process.arch;
const language = 'en-US'; // default language const language = "en-US"; // default language
// Expected pattern: TestApp_1.0.0_x64_en-US.msi // Expected pattern: TestApp_1.0.0_x64_en-US.msi
const expectedPattern = `${mockName}_${mockVersion}_${arch}_${language}`; const expectedPattern = `${mockName}_${mockVersion}_${arch}_${language}`;
return expectedPattern.includes(mockName) && return (
expectedPattern.includes(mockVersion) && expectedPattern.includes(mockName) &&
expectedPattern.includes(arch) && expectedPattern.includes(mockVersion) &&
expectedPattern.includes(language); expectedPattern.includes(arch) &&
expectedPattern.includes(language)
);
} catch (error) { } catch (error) {
return false; return false;
} }
}, },
'Should generate correct Windows file naming pattern' "Should generate correct Windows file naming pattern",
); );
runner.addTest( runner.addTest(
'Linux Builder File Naming Pattern', "Linux Builder File Naming Pattern",
() => { () => {
try { try {
// Test Linux file naming variations // Test Linux file naming variations
const mockName = 'testapp'; const mockName = "testapp";
const mockVersion = '1.0.0'; const mockVersion = "1.0.0";
let arch = process.arch === 'x64' ? 'amd64' : process.arch; let arch = process.arch === "x64" ? "amd64" : process.arch;
// Test different target formats // Test different target formats
const debPattern = `${mockName}_${mockVersion}_${arch}`; // .deb const debPattern = `${mockName}_${mockVersion}_${arch}`; // .deb
const rpmPattern = `${mockName}-${mockVersion}-1.${arch === 'arm64' ? 'aarch64' : arch}`; // .rpm const rpmPattern = `${mockName}-${mockVersion}-1.${arch === "arm64" ? "aarch64" : arch}`; // .rpm
const appImagePattern = `${mockName}_${mockVersion}_${arch === 'arm64' ? 'aarch64' : arch}`; // .AppImage const appImagePattern = `${mockName}_${mockVersion}_${arch === "arm64" ? "aarch64" : arch}`; // .AppImage
return debPattern.includes(mockName) && return (
rpmPattern.includes(mockName) && debPattern.includes(mockName) &&
appImagePattern.includes(mockName); rpmPattern.includes(mockName) &&
appImagePattern.includes(mockName)
);
} catch (error) { } catch (error) {
return false; return false;
} }
}, },
'Should generate correct Linux file naming patterns for different targets' "Should generate correct Linux file naming patterns for different targets",
); );
runner.addTest( runner.addTest(
'Architecture Detection Logic', "Architecture Detection Logic",
() => { () => {
try { try {
// Test architecture mapping logic // Test architecture mapping logic
const currentArch = process.arch; const currentArch = process.arch;
// Mac: arm64 -> aarch64, others keep same // Mac: arm64 -> aarch64, others keep same
const macArch = currentArch === 'arm64' ? 'aarch64' : currentArch; const macArch = currentArch === "arm64" ? "aarch64" : currentArch;
// Linux: x64 -> amd64 for deb, arm64 -> aarch64 for rpm/appimage // Linux: x64 -> amd64 for deb, arm64 -> aarch64 for rpm/appimage
const linuxArch = currentArch === 'x64' ? 'amd64' : currentArch; const linuxArch = currentArch === "x64" ? "amd64" : currentArch;
// Windows: keeps process.arch as-is // Windows: keeps process.arch as-is
const winArch = currentArch; const winArch = currentArch;
return typeof macArch === 'string' && return (
typeof linuxArch === 'string' && typeof macArch === "string" &&
typeof winArch === 'string'; typeof linuxArch === "string" &&
typeof winArch === "string"
);
} catch (error) { } catch (error) {
return false; return false;
} }
}, },
'Should correctly detect and map system architecture' "Should correctly detect and map system architecture",
); );
runner.addTest( runner.addTest(
'Multi-arch Build Detection', "Multi-arch Build Detection",
() => { () => {
try { try {
// Test universal binary logic for macOS // Test universal binary logic for macOS
const platform = process.platform; const platform = process.platform;
if (platform === 'darwin') { if (platform === "darwin") {
// macOS should support multi-arch with --multi-arch flag // macOS should support multi-arch with --multi-arch flag
const supportsMultiArch = true; const supportsMultiArch = true;
const universalSuffix = 'universal'; const universalSuffix = "universal";
return supportsMultiArch && universalSuffix === 'universal'; return supportsMultiArch && universalSuffix === "universal";
} else { } else {
// Other platforms don't support multi-arch // Other platforms don't support multi-arch
return true; return true;
@@ -186,98 +205,103 @@ runner.addTest(
return false; return false;
} }
}, },
'Should handle multi-architecture builds correctly' "Should handle multi-architecture builds correctly",
); );
runner.addTest( runner.addTest(
'Target Format Validation', "Target Format Validation",
() => { () => {
try { try {
// Test valid target formats per platform // Test valid target formats per platform
const platform = process.platform; const platform = process.platform;
const validTargets = { const validTargets = {
'darwin': ['dmg'], darwin: ["dmg"],
'win32': ['msi'], win32: ["msi"],
'linux': ['deb', 'appimage', 'rpm'] linux: ["deb", "appimage", "rpm"],
}; };
const platformTargets = validTargets[platform]; const platformTargets = validTargets[platform];
return Array.isArray(platformTargets) && platformTargets.length > 0; return Array.isArray(platformTargets) && platformTargets.length > 0;
} catch (error) { } catch (error) {
return false; return false;
} }
}, },
'Should validate target formats per platform' "Should validate target formats per platform",
); );
runner.addTest( runner.addTest(
'Build Path Generation', "Build Path Generation",
() => { () => {
try { try {
// Test build path logic: debug vs release // Test build path logic: debug vs release
const debugPath = 'src-tauri/target/debug/bundle/'; const debugPath = "src-tauri/target/debug/bundle/";
const releasePath = 'src-tauri/target/release/bundle/'; const releasePath = "src-tauri/target/release/bundle/";
const universalPath = 'src-tauri/target/universal-apple-darwin/release/bundle'; const universalPath =
"src-tauri/target/universal-apple-darwin/release/bundle";
// Paths should be consistent // Paths should be consistent
return debugPath.includes('debug') && return (
releasePath.includes('release') && debugPath.includes("debug") &&
universalPath.includes('universal'); releasePath.includes("release") &&
universalPath.includes("universal")
);
} catch (error) { } catch (error) {
return false; return false;
} }
}, },
'Should generate correct build paths for different modes' "Should generate correct build paths for different modes",
); );
runner.addTest( runner.addTest(
'File Extension Mapping', "File Extension Mapping",
() => { () => {
try { try {
// Test file extension mapping logic // Test file extension mapping logic
const platform = process.platform; const platform = process.platform;
const extensionMap = { const extensionMap = {
'darwin': 'dmg', darwin: "dmg",
'win32': 'msi', win32: "msi",
'linux': 'deb' // default, can be appimage or rpm linux: "deb", // default, can be appimage or rpm
}; };
const expectedExt = extensionMap[platform]; const expectedExt = extensionMap[platform];
// Special case for Linux AppImage (capital A) // Special case for Linux AppImage (capital A)
const appImageExt = 'AppImage'; const appImageExt = "AppImage";
return typeof expectedExt === 'string' && return (
(expectedExt.length > 0 || appImageExt === 'AppImage'); typeof expectedExt === "string" &&
(expectedExt.length > 0 || appImageExt === "AppImage")
);
} catch (error) { } catch (error) {
return false; return false;
} }
}, },
'Should map file extensions correctly per platform' "Should map file extensions correctly per platform",
); );
runner.addTest( runner.addTest(
'Name Sanitization Logic', "Name Sanitization Logic",
() => { () => {
try { try {
// Test name sanitization for file systems // Test name sanitization for file systems
const testNames = [ const testNames = [
'Simple App', // Should handle spaces "Simple App", // Should handle spaces
'App-With_Symbols', // Should handle hyphens and underscores "App-With_Symbols", // Should handle hyphens and underscores
'CamelCaseApp', // Should handle case variations "CamelCaseApp", // Should handle case variations
'App123', // Should handle numbers "App123", // Should handle numbers
]; ];
// Test that names can be processed (basic validation) // Test that names can be processed (basic validation)
return testNames.every(name => { return testNames.every((name) => {
const processed = name.toLowerCase().replace(/\s+/g, ''); const processed = name.toLowerCase().replace(/\s+/g, "");
return processed.length > 0; return processed.length > 0;
}); });
} catch (error) { } catch (error) {
return false; return false;
} }
}, },
'Should sanitize app names for filesystem compatibility' "Should sanitize app names for filesystem compatibility",
); );
// Run builder tests if this file is executed directly // Run builder tests if this file is executed directly
@@ -285,4 +309,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
runner.runAll().catch(console.error); runner.runAll().catch(console.error);
} }
export default runner; export default runner;

File diff suppressed because it is too large Load Diff

View File

@@ -7,71 +7,72 @@
* Usage: node tests/index.js [--unit] [--integration] [--manual] * Usage: node tests/index.js [--unit] [--integration] [--manual]
*/ */
import cliTestRunner from './cli.test.js'; import cliTestRunner from "./cli.test.js";
import integrationTestRunner from './integration.test.js'; import integrationTestRunner from "./integration.test.js";
import builderTestRunner from './builder.test.js'; import builderTestRunner from "./builder.test.js";
import { execSync } from 'child_process'; import { execSync } from "child_process";
import path from 'path'; import path from "path";
import { fileURLToPath } from 'url'; import { fileURLToPath } from "url";
import ora from 'ora'; import ora from "ora";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.dirname(__dirname); const projectRoot = path.dirname(__dirname);
const cliPath = path.join(projectRoot, 'dist/cli.js'); const cliPath = path.join(projectRoot, "dist/cli.js");
const args = process.argv.slice(2); const args = process.argv.slice(2);
const runUnit = args.length === 0 || args.includes('--unit'); const runUnit = args.length === 0 || args.includes("--unit");
const runIntegration = args.length === 0 || args.includes('--integration'); const runIntegration = args.length === 0 || args.includes("--integration");
const runBuilder = args.length === 0 || args.includes('--builder'); const runBuilder = args.length === 0 || args.includes("--builder");
async function runAllTests() { async function runAllTests() {
console.log('🚀 Pake CLI Test Suite'); console.log("🚀 Pake CLI Test Suite");
console.log('=======================\n'); console.log("=======================\n");
let totalPassed = 0; let totalPassed = 0;
let totalTests = 0; let totalTests = 0;
if (runUnit) { if (runUnit) {
console.log('📋 Running Unit Tests...\n'); console.log("📋 Running Unit Tests...\n");
await cliTestRunner.runAll(); await cliTestRunner.runAll();
totalPassed += cliTestRunner.results.filter(r => r.passed).length; totalPassed += cliTestRunner.results.filter((r) => r.passed).length;
totalTests += cliTestRunner.results.length; totalTests += cliTestRunner.results.length;
console.log(''); console.log("");
} }
if (runIntegration) { if (runIntegration) {
console.log('🔧 Running Integration Tests...\n'); console.log("🔧 Running Integration Tests...\n");
await integrationTestRunner.runAll(); await integrationTestRunner.runAll();
totalPassed += integrationTestRunner.results.filter(r => r.passed).length; totalPassed += integrationTestRunner.results.filter((r) => r.passed).length;
totalTests += integrationTestRunner.results.length; totalTests += integrationTestRunner.results.length;
console.log(''); console.log("");
} }
if (runBuilder) { if (runBuilder) {
console.log('🏗️ Running Builder Tests...\n'); console.log("🏗️ Running Builder Tests...\n");
await builderTestRunner.runAll(); await builderTestRunner.runAll();
totalPassed += builderTestRunner.results.filter(r => r.passed).length; totalPassed += builderTestRunner.results.filter((r) => r.passed).length;
totalTests += builderTestRunner.results.length; totalTests += builderTestRunner.results.length;
console.log(''); console.log("");
} }
// Final summary // Final summary
console.log('🎯 Overall Test Summary'); console.log("🎯 Overall Test Summary");
console.log('======================='); console.log("=======================");
console.log(`Total: ${totalPassed}/${totalTests} tests passed`); console.log(`Total: ${totalPassed}/${totalTests} tests passed`);
if (totalPassed === totalTests) { if (totalPassed === totalTests) {
console.log('🎉 All tests passed! CLI is ready for use.\n'); console.log("🎉 All tests passed! CLI is ready for use.\n");
} else { } else {
console.log(`${totalTests - totalPassed} test(s) failed. Please check the issues above.\n`); console.log(
`${totalTests - totalPassed} test(s) failed. Please check the issues above.\n`,
);
} }
// Exit with appropriate code // Exit with appropriate code
process.exit(totalPassed === totalTests ? 0 : 1); process.exit(totalPassed === totalTests ? 0 : 1);
} }
runAllTests().catch(error => { runAllTests().catch((error) => {
console.error('❌ Test runner failed:', error); console.error("❌ Test runner failed:", error);
process.exit(1); process.exit(1);
}); });

View File

@@ -2,16 +2,16 @@
/** /**
* Integration Tests for Pake CLI * Integration Tests for Pake CLI
* *
* These tests verify that different components work together correctly. * These tests verify that different components work together correctly.
* They may take longer to run as they test actual build processes. * They may take longer to run as they test actual build processes.
*/ */
import { spawn, execSync } from 'child_process'; import { spawn, execSync } from "child_process";
import fs from 'fs'; import fs from "fs";
import path from 'path'; import path from "path";
import config, { TIMEOUTS, TEST_URLS } from './test.config.js'; import config, { TIMEOUTS, TEST_URLS } from "./test.config.js";
import ora from 'ora'; import ora from "ora";
class IntegrationTestRunner { class IntegrationTestRunner {
constructor() { constructor() {
@@ -25,20 +25,20 @@ class IntegrationTestRunner {
} }
async runAll() { async runAll() {
console.log('🔧 Integration Tests'); console.log("🔧 Integration Tests");
console.log('====================\n'); console.log("====================\n");
for (const [index, test] of this.tests.entries()) { for (const [index, test] of this.tests.entries()) {
const spinner = ora(`Running ${test.name}...`).start(); const spinner = ora(`Running ${test.name}...`).start();
try { try {
const result = await Promise.race([ const result = await Promise.race([
test.testFn(), test.testFn(),
new Promise((_, reject) => new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), test.timeout) setTimeout(() => reject(new Error("Timeout")), test.timeout),
) ),
]); ]);
if (result) { if (result) {
spinner.succeed(`${index + 1}. ${test.name}: PASS`); spinner.succeed(`${index + 1}. ${test.name}: PASS`);
this.results.push({ name: test.name, passed: true }); this.results.push({ name: test.name, passed: true });
@@ -47,8 +47,14 @@ class IntegrationTestRunner {
this.results.push({ name: test.name, passed: false }); this.results.push({ name: test.name, passed: false });
} }
} catch (error) { } catch (error) {
spinner.fail(`${index + 1}. ${test.name}: ERROR - ${error.message.slice(0, 50)}...`); spinner.fail(
this.results.push({ name: test.name, passed: false, error: error.message }); `${index + 1}. ${test.name}: ERROR - ${error.message.slice(0, 50)}...`,
);
this.results.push({
name: test.name,
passed: false,
error: error.message,
});
} }
} }
@@ -58,7 +64,7 @@ class IntegrationTestRunner {
cleanup() { cleanup() {
// Clean up any temporary files created during tests // Clean up any temporary files created during tests
this.tempFiles.forEach(file => { this.tempFiles.forEach((file) => {
try { try {
if (fs.existsSync(file)) { if (fs.existsSync(file)) {
fs.unlinkSync(file); fs.unlinkSync(file);
@@ -70,18 +76,22 @@ class IntegrationTestRunner {
} }
displayResults() { displayResults() {
const passed = this.results.filter(r => r.passed).length; const passed = this.results.filter((r) => r.passed).length;
const total = this.results.length; const total = this.results.length;
console.log(`\n📊 Integration Test Results: ${passed}/${total} passed\n`); console.log(`\n📊 Integration Test Results: ${passed}/${total} passed\n`);
if (passed === total) { if (passed === total) {
console.log('🎉 All integration tests passed!'); console.log("🎉 All integration tests passed!");
} else { } else {
console.log('❌ Some integration tests failed'); console.log("❌ Some integration tests failed");
this.results.filter(r => !r.passed).forEach(result => { this.results
console.log(` - ${result.name}${result.error ? `: ${result.error}` : ''}`); .filter((r) => !r.passed)
}); .forEach((result) => {
console.log(
` - ${result.name}${result.error ? `: ${result.error}` : ""}`,
);
});
} }
} }
@@ -93,60 +103,57 @@ class IntegrationTestRunner {
const runner = new IntegrationTestRunner(); const runner = new IntegrationTestRunner();
// Integration Tests // Integration Tests
runner.addTest( runner.addTest("CLI Process Spawning", () => {
'CLI Process Spawning', return new Promise((resolve) => {
() => { const child = spawn("node", [config.CLI_PATH, "--version"], {
return new Promise((resolve) => { stdio: ["pipe", "pipe", "pipe"],
const child = spawn('node', [config.CLI_PATH, '--version'], {
stdio: ['pipe', 'pipe', 'pipe']
});
let output = '';
child.stdout.on('data', (data) => {
output += data.toString();
});
child.on('close', (code) => {
resolve(code === 0 && /\d+\.\d+\.\d+/.test(output));
});
// Kill after 3 seconds if still running
setTimeout(() => {
child.kill();
resolve(false);
}, 3000);
}); });
}
); let output = "";
child.stdout.on("data", (data) => {
output += data.toString();
});
child.on("close", (code) => {
resolve(code === 0 && /\d+\.\d+\.\d+/.test(output));
});
// Kill after 3 seconds if still running
setTimeout(() => {
child.kill();
resolve(false);
}, 3000);
});
});
runner.addTest( runner.addTest(
'Interactive Mode Simulation', "Interactive Mode Simulation",
() => { () => {
return new Promise((resolve) => { return new Promise((resolve) => {
const child = spawn('node', [config.CLI_PATH, TEST_URLS.WEEKLY], { const child = spawn("node", [config.CLI_PATH, TEST_URLS.WEEKLY], {
stdio: ['pipe', 'pipe', 'pipe'] stdio: ["pipe", "pipe", "pipe"],
}); });
let output = ''; let output = "";
let prompted = false; let prompted = false;
child.stdout.on('data', (data) => { child.stdout.on("data", (data) => {
output += data.toString(); output += data.toString();
// If we see a prompt for application name, provide input // If we see a prompt for application name, provide input
if (output.includes('Enter your application name') && !prompted) { if (output.includes("Enter your application name") && !prompted) {
prompted = true; prompted = true;
child.stdin.write('TestApp\n'); child.stdin.write("TestApp\n");
setTimeout(() => { setTimeout(() => {
child.kill(); child.kill();
resolve(true); resolve(true);
}, 1000); }, 1000);
} }
}); });
child.on('close', () => { child.on("close", () => {
resolve(prompted); resolve(prompted);
}); });
// Timeout after 10 seconds // Timeout after 10 seconds
setTimeout(() => { setTimeout(() => {
child.kill(); child.kill();
@@ -154,86 +161,80 @@ runner.addTest(
}, 10000); }, 10000);
}); });
}, },
TIMEOUTS.MEDIUM TIMEOUTS.MEDIUM,
); );
runner.addTest( runner.addTest(
'Command Line Argument Parsing', "Command Line Argument Parsing",
() => { () => {
try { try {
// Test argument validation by running CLI with --help to verify args are parsed // Test argument validation by running CLI with --help to verify args are parsed
const helpOutput = execSync(`node "${config.CLI_PATH}" --help`, { const helpOutput = execSync(`node "${config.CLI_PATH}" --help`, {
encoding: 'utf8', encoding: "utf8",
timeout: 3000 timeout: 3000,
}); });
// Verify that our command structure is valid by checking help includes our options // Verify that our command structure is valid by checking help includes our options
const validOptions = ['--width', '--height', '--debug', '--name'].every(opt => const validOptions = ["--width", "--height", "--debug", "--name"].every(
helpOutput.includes(opt) (opt) => helpOutput.includes(opt),
); );
return validOptions; return validOptions;
} catch (error) { } catch (error) {
return false; return false;
} }
}, },
TIMEOUTS.QUICK TIMEOUTS.QUICK,
); );
runner.addTest( runner.addTest("File System Permissions", () => {
'File System Permissions', try {
() => { // Test that we can write to current directory
try { const testFile = "test-write-permission.tmp";
// Test that we can write to current directory fs.writeFileSync(testFile, "test");
const testFile = 'test-write-permission.tmp'; runner.trackTempFile(testFile);
fs.writeFileSync(testFile, 'test');
runner.trackTempFile(testFile);
// Test that we can read from CLI directory
const cliStats = fs.statSync(config.CLI_PATH);
return cliStats.isFile();
} catch (error) {
return false;
}
}
);
runner.addTest( // Test that we can read from CLI directory
'Dependency Resolution', const cliStats = fs.statSync(config.CLI_PATH);
() => { return cliStats.isFile();
try { } catch (error) {
// Verify that essential runtime dependencies are available return false;
const packageJsonPath = path.join(config.PROJECT_ROOT, 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const essentialDeps = [
'commander',
'chalk',
'fs-extra',
'execa',
'prompts'
];
return essentialDeps.every(dep => {
try {
// Try to resolve the dependency
import.meta.resolve ?
import.meta.resolve(dep) :
require.resolve(dep, { paths: [config.PROJECT_ROOT] });
return true;
} catch {
return packageJson.dependencies && packageJson.dependencies[dep];
}
});
} catch {
return false;
}
} }
); });
runner.addTest("Dependency Resolution", () => {
try {
// Verify that essential runtime dependencies are available
const packageJsonPath = path.join(config.PROJECT_ROOT, "package.json");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
const essentialDeps = [
"commander",
"chalk",
"fs-extra",
"execa",
"prompts",
];
return essentialDeps.every((dep) => {
try {
// Try to resolve the dependency
import.meta.resolve
? import.meta.resolve(dep)
: require.resolve(dep, { paths: [config.PROJECT_ROOT] });
return true;
} catch {
return packageJson.dependencies && packageJson.dependencies[dep];
}
});
} catch {
return false;
}
});
// Run integration tests if this file is executed directly // Run integration tests if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) { if (import.meta.url === `file://${process.argv[1]}`) {
runner.runAll().catch(console.error); runner.runAll().catch(console.error);
} }
export default runner; export default runner;

View File

@@ -1,55 +1,55 @@
/** /**
* Test Configuration for Pake CLI * Test Configuration for Pake CLI
* *
* This file contains test configuration and utilities * This file contains test configuration and utilities
* shared across different test files. * shared across different test files.
*/ */
import path from 'path'; import path from "path";
import { fileURLToPath } from 'url'; import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
export const PROJECT_ROOT = path.dirname(__dirname); export const PROJECT_ROOT = path.dirname(__dirname);
export const CLI_PATH = path.join(PROJECT_ROOT, 'dist/cli.js'); export const CLI_PATH = path.join(PROJECT_ROOT, "dist/cli.js");
// Test timeouts (in milliseconds) // Test timeouts (in milliseconds)
export const TIMEOUTS = { export const TIMEOUTS = {
QUICK: 3000, // For version, help commands QUICK: 3000, // For version, help commands
MEDIUM: 10000, // For validation tests MEDIUM: 10000, // For validation tests
LONG: 300000, // For build tests (5 minutes) LONG: 300000, // For build tests (5 minutes)
}; };
// Test URLs for different scenarios // Test URLs for different scenarios
export const TEST_URLS = { export const TEST_URLS = {
WEEKLY: 'https://weekly.tw93.fun', WEEKLY: "https://weekly.tw93.fun",
VALID: 'https://example.com', VALID: "https://example.com",
GITHUB: 'https://github.com', GITHUB: "https://github.com",
GOOGLE: 'https://www.google.com', GOOGLE: "https://www.google.com",
INVALID: 'not://a/valid[url]', INVALID: "not://a/valid[url]",
LOCAL: './test-file.html' LOCAL: "./test-file.html",
}; };
// Test assets for different scenarios // Test assets for different scenarios
export const TEST_ASSETS = { export const TEST_ASSETS = {
WEEKLY_ICNS: 'https://gw.alipayobjects.com/os/k/fw/weekly.icns', WEEKLY_ICNS: "https://gw.alipayobjects.com/os/k/fw/weekly.icns",
INVALID_ICON: 'https://example.com/nonexistent.icns' INVALID_ICON: "https://example.com/nonexistent.icns",
}; };
// Test app names // Test app names
export const TEST_NAMES = { export const TEST_NAMES = {
WEEKLY: 'Weekly', WEEKLY: "Weekly",
BASIC: 'TestApp', BASIC: "TestApp",
DEBUG: 'DebugApp', DEBUG: "DebugApp",
FULL: 'FullscreenApp', FULL: "FullscreenApp",
GOOGLE_TRANSLATE: 'Google Translate', GOOGLE_TRANSLATE: "Google Translate",
MAC: 'MacApp' MAC: "MacApp",
}; };
// Expected file extensions by platform // Expected file extensions by platform
export const PLATFORM_EXTENSIONS = { export const PLATFORM_EXTENSIONS = {
darwin: 'dmg', darwin: "dmg",
win32: 'msi', win32: "msi",
linux: 'deb' linux: "deb",
}; };
// Helper functions // Helper functions
@@ -57,16 +57,16 @@ export const testHelpers = {
/** /**
* Clean test name for filesystem * Clean test name for filesystem
*/ */
sanitizeName: (name) => name.replace(/[^a-zA-Z0-9]/g, ''), sanitizeName: (name) => name.replace(/[^a-zA-Z0-9]/g, ""),
/** /**
* Get expected output file for current platform * Get expected output file for current platform
*/ */
getExpectedOutput: (appName) => { getExpectedOutput: (appName) => {
const ext = PLATFORM_EXTENSIONS[process.platform] || 'bin'; const ext = PLATFORM_EXTENSIONS[process.platform] || "bin";
return `${appName}.${ext}`; return `${appName}.${ext}`;
}, },
/** /**
* Create test command with common options * Create test command with common options
*/ */
@@ -75,14 +75,14 @@ export const testHelpers = {
const optionsStr = Object.entries(options) const optionsStr = Object.entries(options)
.map(([key, value]) => { .map(([key, value]) => {
if (value === true) return `--${key}`; if (value === true) return `--${key}`;
if (value === false) return ''; if (value === false) return "";
return `--${key} "${value}"`; return `--${key} "${value}"`;
}) })
.filter(Boolean) .filter(Boolean)
.join(' '); .join(" ");
return `${baseCmd} ${optionsStr}`.trim(); return `${baseCmd} ${optionsStr}`.trim();
} },
}; };
export default { export default {
@@ -92,5 +92,5 @@ export default {
TEST_URLS, TEST_URLS,
TEST_NAMES, TEST_NAMES,
PLATFORM_EXTENSIONS, PLATFORM_EXTENSIONS,
testHelpers testHelpers,
}; };