🎨 format code
This commit is contained in:
4
.github/workflows/claude-unified.yml
vendored
4
.github/workflows/claude-unified.yml
vendored
@@ -66,6 +66,6 @@ jobs:
|
||||
- Performance considerations for desktop app packaging
|
||||
- CLI usability and error handling
|
||||
- Test coverage completeness
|
||||
|
||||
|
||||
Be constructive and helpful in your feedback.
|
||||
use_sticky_comment: true
|
||||
use_sticky_comment: true
|
||||
|
||||
10
.github/workflows/quality-and-test.yml
vendored
10
.github/workflows/quality-and-test.yml
vendored
@@ -98,7 +98,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
cache: "npm"
|
||||
|
||||
- name: Install Rust (Ubuntu)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
@@ -157,21 +157,21 @@ jobs:
|
||||
run: |
|
||||
echo "# 🎯 Quality & Testing Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
|
||||
if [ "${{ needs.formatting.result }}" == "success" ]; then
|
||||
echo "✅ **Code Formatting**: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Code Formatting**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
|
||||
if [ "${{ needs.rust-quality.result }}" == "success" ]; then
|
||||
echo "✅ **Rust Quality**: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Rust Quality**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
|
||||
if [ "${{ needs.cli-tests.result }}" == "success" ]; then
|
||||
echo "✅ **CLI Tests**: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **CLI Tests**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -113,4 +113,4 @@ jobs:
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64
|
||||
platforms: linux/amd64
|
||||
|
||||
@@ -79,7 +79,7 @@ npm run build:mac # macOS universal build
|
||||
- [ ] Run `npm test` for comprehensive validation (35 tests)
|
||||
- [ ] Test on target platforms
|
||||
- [ ] Verify injection system works
|
||||
- [ ] Check system tray integration
|
||||
- [ ] Check system tray integration
|
||||
- [ ] Validate window behavior
|
||||
- [ ] Test with weekly.tw93.fun URL
|
||||
- [ ] Verify remote icon functionality (https://gw.alipayobjects.com/os/k/fw/weekly.icns)
|
||||
|
||||
@@ -48,7 +48,7 @@ npm run dev
|
||||
# Run all tests (unit + integration + builder)
|
||||
npm test
|
||||
|
||||
# Build CLI for testing
|
||||
# Build CLI for testing
|
||||
npm run cli:build
|
||||
```
|
||||
|
||||
|
||||
5
bin/builders/BaseBuilder.ts
vendored
5
bin/builders/BaseBuilder.ts
vendored
@@ -54,7 +54,9 @@ export default abstract class BaseBuilder {
|
||||
|
||||
// For global CLI installation, always use npm
|
||||
const packageManager = 'npm';
|
||||
const registryOption = isChina ? ' --registry=https://registry.npmmirror.com' : '';
|
||||
const registryOption = isChina
|
||||
? ' --registry=https://registry.npmmirror.com'
|
||||
: '';
|
||||
|
||||
if (isChina) {
|
||||
logger.info('✺ Located in China, using npm/rsProxy CN mirror.');
|
||||
@@ -130,5 +132,4 @@ export default abstract class BaseBuilder {
|
||||
`${fileName}.${fileType}`,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ runner.addTest(
|
||||
// Test logic here
|
||||
return true; // or false
|
||||
},
|
||||
"Test description"
|
||||
"Test description",
|
||||
);
|
||||
```
|
||||
|
||||
@@ -168,7 +168,7 @@ runner.addTest(
|
||||
// Async test logic
|
||||
return await someAsyncOperation();
|
||||
},
|
||||
TIMEOUTS.MEDIUM
|
||||
TIMEOUTS.MEDIUM,
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
/**
|
||||
* Builder-specific Tests for Pake CLI
|
||||
*
|
||||
*
|
||||
* These tests verify platform-specific builder logic and file naming patterns
|
||||
* Based on analysis of bin/builders/ implementation
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import path from 'path';
|
||||
import config, { TEST_URLS, TEST_NAMES } from './test.config.js';
|
||||
import ora from 'ora';
|
||||
import { execSync } from "child_process";
|
||||
import path from "path";
|
||||
import config, { TEST_URLS, TEST_NAMES } from "./test.config.js";
|
||||
import ora from "ora";
|
||||
|
||||
class BuilderTestRunner {
|
||||
constructor() {
|
||||
@@ -18,17 +18,17 @@ class BuilderTestRunner {
|
||||
this.results = [];
|
||||
}
|
||||
|
||||
addTest(name, testFn, description = '') {
|
||||
addTest(name, testFn, description = "") {
|
||||
this.tests.push({ name, testFn, description });
|
||||
}
|
||||
|
||||
async runAll() {
|
||||
console.log('🏗️ Builder-specific Tests');
|
||||
console.log('==========================\n');
|
||||
console.log("🏗️ Builder-specific Tests");
|
||||
console.log("==========================\n");
|
||||
|
||||
for (const [index, test] of this.tests.entries()) {
|
||||
const spinner = ora(`Running ${test.name}...`).start();
|
||||
|
||||
|
||||
try {
|
||||
const result = await test.testFn();
|
||||
if (result) {
|
||||
@@ -39,8 +39,14 @@ class BuilderTestRunner {
|
||||
this.results.push({ name: test.name, passed: false });
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail(`${index + 1}. ${test.name}: ERROR - ${error.message.slice(0, 50)}...`);
|
||||
this.results.push({ name: test.name, passed: false, error: error.message });
|
||||
spinner.fail(
|
||||
`${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() {
|
||||
const passed = this.results.filter(r => r.passed).length;
|
||||
const passed = this.results.filter((r) => r.passed).length;
|
||||
const total = this.results.length;
|
||||
|
||||
console.log(`\n📊 Builder Test Results: ${passed}/${total} passed\n`);
|
||||
|
||||
|
||||
if (passed === total) {
|
||||
console.log('🎉 All builder tests passed!');
|
||||
console.log("🎉 All builder tests passed!");
|
||||
} else {
|
||||
console.log('❌ Some builder tests failed');
|
||||
this.results.filter(r => !r.passed).forEach(result => {
|
||||
console.log(` - ${result.name}${result.error ? `: ${result.error}` : ''}`);
|
||||
});
|
||||
console.log("❌ Some builder tests failed");
|
||||
this.results
|
||||
.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
|
||||
runner.addTest(
|
||||
'Mac Builder File Naming Pattern',
|
||||
"Mac Builder File Naming Pattern",
|
||||
() => {
|
||||
try {
|
||||
// Test macOS file naming: name_version_arch.dmg
|
||||
const mockName = 'TestApp';
|
||||
const mockVersion = '1.0.0';
|
||||
const arch = process.arch === 'arm64' ? 'aarch64' : process.arch;
|
||||
|
||||
const mockName = "TestApp";
|
||||
const mockVersion = "1.0.0";
|
||||
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)
|
||||
const expectedPattern = `${mockName}_${mockVersion}_${arch}`;
|
||||
const universalPattern = `${mockName}_${mockVersion}_universal`;
|
||||
|
||||
|
||||
// Test that naming pattern is consistent
|
||||
return expectedPattern.includes(mockName) &&
|
||||
expectedPattern.includes(mockVersion) &&
|
||||
(expectedPattern.includes(arch) || universalPattern.includes('universal'));
|
||||
return (
|
||||
expectedPattern.includes(mockName) &&
|
||||
expectedPattern.includes(mockVersion) &&
|
||||
(expectedPattern.includes(arch) ||
|
||||
universalPattern.includes("universal"))
|
||||
);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'Should generate correct macOS file naming pattern'
|
||||
"Should generate correct macOS file naming pattern",
|
||||
);
|
||||
|
||||
runner.addTest(
|
||||
'Windows Builder File Naming Pattern',
|
||||
"Windows Builder File Naming Pattern",
|
||||
() => {
|
||||
try {
|
||||
// Test Windows file naming: name_version_arch_language.msi
|
||||
const mockName = 'TestApp';
|
||||
const mockVersion = '1.0.0';
|
||||
const mockName = "TestApp";
|
||||
const mockVersion = "1.0.0";
|
||||
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
|
||||
const expectedPattern = `${mockName}_${mockVersion}_${arch}_${language}`;
|
||||
|
||||
return expectedPattern.includes(mockName) &&
|
||||
expectedPattern.includes(mockVersion) &&
|
||||
expectedPattern.includes(arch) &&
|
||||
expectedPattern.includes(language);
|
||||
|
||||
return (
|
||||
expectedPattern.includes(mockName) &&
|
||||
expectedPattern.includes(mockVersion) &&
|
||||
expectedPattern.includes(arch) &&
|
||||
expectedPattern.includes(language)
|
||||
);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'Should generate correct Windows file naming pattern'
|
||||
"Should generate correct Windows file naming pattern",
|
||||
);
|
||||
|
||||
runner.addTest(
|
||||
'Linux Builder File Naming Pattern',
|
||||
"Linux Builder File Naming Pattern",
|
||||
() => {
|
||||
try {
|
||||
// Test Linux file naming variations
|
||||
const mockName = 'testapp';
|
||||
const mockVersion = '1.0.0';
|
||||
let arch = process.arch === 'x64' ? 'amd64' : process.arch;
|
||||
|
||||
const mockName = "testapp";
|
||||
const mockVersion = "1.0.0";
|
||||
let arch = process.arch === "x64" ? "amd64" : process.arch;
|
||||
|
||||
// Test different target formats
|
||||
const debPattern = `${mockName}_${mockVersion}_${arch}`; // .deb
|
||||
const rpmPattern = `${mockName}-${mockVersion}-1.${arch === 'arm64' ? 'aarch64' : arch}`; // .rpm
|
||||
const appImagePattern = `${mockName}_${mockVersion}_${arch === 'arm64' ? 'aarch64' : arch}`; // .AppImage
|
||||
|
||||
return debPattern.includes(mockName) &&
|
||||
rpmPattern.includes(mockName) &&
|
||||
appImagePattern.includes(mockName);
|
||||
const rpmPattern = `${mockName}-${mockVersion}-1.${arch === "arm64" ? "aarch64" : arch}`; // .rpm
|
||||
const appImagePattern = `${mockName}_${mockVersion}_${arch === "arm64" ? "aarch64" : arch}`; // .AppImage
|
||||
|
||||
return (
|
||||
debPattern.includes(mockName) &&
|
||||
rpmPattern.includes(mockName) &&
|
||||
appImagePattern.includes(mockName)
|
||||
);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'Should generate correct Linux file naming patterns for different targets'
|
||||
"Should generate correct Linux file naming patterns for different targets",
|
||||
);
|
||||
|
||||
runner.addTest(
|
||||
'Architecture Detection Logic',
|
||||
"Architecture Detection Logic",
|
||||
() => {
|
||||
try {
|
||||
// Test architecture mapping logic
|
||||
const currentArch = process.arch;
|
||||
|
||||
|
||||
// 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
|
||||
const linuxArch = currentArch === 'x64' ? 'amd64' : currentArch;
|
||||
|
||||
const linuxArch = currentArch === "x64" ? "amd64" : currentArch;
|
||||
|
||||
// Windows: keeps process.arch as-is
|
||||
const winArch = currentArch;
|
||||
|
||||
return typeof macArch === 'string' &&
|
||||
typeof linuxArch === 'string' &&
|
||||
typeof winArch === 'string';
|
||||
|
||||
return (
|
||||
typeof macArch === "string" &&
|
||||
typeof linuxArch === "string" &&
|
||||
typeof winArch === "string"
|
||||
);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'Should correctly detect and map system architecture'
|
||||
"Should correctly detect and map system architecture",
|
||||
);
|
||||
|
||||
runner.addTest(
|
||||
'Multi-arch Build Detection',
|
||||
"Multi-arch Build Detection",
|
||||
() => {
|
||||
try {
|
||||
// Test universal binary logic for macOS
|
||||
const platform = process.platform;
|
||||
|
||||
if (platform === 'darwin') {
|
||||
|
||||
if (platform === "darwin") {
|
||||
// macOS should support multi-arch with --multi-arch flag
|
||||
const supportsMultiArch = true;
|
||||
const universalSuffix = 'universal';
|
||||
|
||||
return supportsMultiArch && universalSuffix === 'universal';
|
||||
const universalSuffix = "universal";
|
||||
|
||||
return supportsMultiArch && universalSuffix === "universal";
|
||||
} else {
|
||||
// Other platforms don't support multi-arch
|
||||
return true;
|
||||
@@ -186,98 +205,103 @@ runner.addTest(
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'Should handle multi-architecture builds correctly'
|
||||
"Should handle multi-architecture builds correctly",
|
||||
);
|
||||
|
||||
runner.addTest(
|
||||
'Target Format Validation',
|
||||
"Target Format Validation",
|
||||
() => {
|
||||
try {
|
||||
// Test valid target formats per platform
|
||||
const platform = process.platform;
|
||||
const validTargets = {
|
||||
'darwin': ['dmg'],
|
||||
'win32': ['msi'],
|
||||
'linux': ['deb', 'appimage', 'rpm']
|
||||
darwin: ["dmg"],
|
||||
win32: ["msi"],
|
||||
linux: ["deb", "appimage", "rpm"],
|
||||
};
|
||||
|
||||
|
||||
const platformTargets = validTargets[platform];
|
||||
return Array.isArray(platformTargets) && platformTargets.length > 0;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'Should validate target formats per platform'
|
||||
"Should validate target formats per platform",
|
||||
);
|
||||
|
||||
runner.addTest(
|
||||
'Build Path Generation',
|
||||
"Build Path Generation",
|
||||
() => {
|
||||
try {
|
||||
// Test build path logic: debug vs release
|
||||
const debugPath = 'src-tauri/target/debug/bundle/';
|
||||
const releasePath = 'src-tauri/target/release/bundle/';
|
||||
const universalPath = 'src-tauri/target/universal-apple-darwin/release/bundle';
|
||||
|
||||
const debugPath = "src-tauri/target/debug/bundle/";
|
||||
const releasePath = "src-tauri/target/release/bundle/";
|
||||
const universalPath =
|
||||
"src-tauri/target/universal-apple-darwin/release/bundle";
|
||||
|
||||
// Paths should be consistent
|
||||
return debugPath.includes('debug') &&
|
||||
releasePath.includes('release') &&
|
||||
universalPath.includes('universal');
|
||||
return (
|
||||
debugPath.includes("debug") &&
|
||||
releasePath.includes("release") &&
|
||||
universalPath.includes("universal")
|
||||
);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'Should generate correct build paths for different modes'
|
||||
"Should generate correct build paths for different modes",
|
||||
);
|
||||
|
||||
runner.addTest(
|
||||
'File Extension Mapping',
|
||||
"File Extension Mapping",
|
||||
() => {
|
||||
try {
|
||||
// Test file extension mapping logic
|
||||
const platform = process.platform;
|
||||
const extensionMap = {
|
||||
'darwin': 'dmg',
|
||||
'win32': 'msi',
|
||||
'linux': 'deb' // default, can be appimage or rpm
|
||||
darwin: "dmg",
|
||||
win32: "msi",
|
||||
linux: "deb", // default, can be appimage or rpm
|
||||
};
|
||||
|
||||
|
||||
const expectedExt = extensionMap[platform];
|
||||
|
||||
|
||||
// Special case for Linux AppImage (capital A)
|
||||
const appImageExt = 'AppImage';
|
||||
|
||||
return typeof expectedExt === 'string' &&
|
||||
(expectedExt.length > 0 || appImageExt === 'AppImage');
|
||||
const appImageExt = "AppImage";
|
||||
|
||||
return (
|
||||
typeof expectedExt === "string" &&
|
||||
(expectedExt.length > 0 || appImageExt === "AppImage")
|
||||
);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'Should map file extensions correctly per platform'
|
||||
"Should map file extensions correctly per platform",
|
||||
);
|
||||
|
||||
runner.addTest(
|
||||
'Name Sanitization Logic',
|
||||
"Name Sanitization Logic",
|
||||
() => {
|
||||
try {
|
||||
// Test name sanitization for file systems
|
||||
const testNames = [
|
||||
'Simple App', // Should handle spaces
|
||||
'App-With_Symbols', // Should handle hyphens and underscores
|
||||
'CamelCaseApp', // Should handle case variations
|
||||
'App123', // Should handle numbers
|
||||
"Simple App", // Should handle spaces
|
||||
"App-With_Symbols", // Should handle hyphens and underscores
|
||||
"CamelCaseApp", // Should handle case variations
|
||||
"App123", // Should handle numbers
|
||||
];
|
||||
|
||||
|
||||
// Test that names can be processed (basic validation)
|
||||
return testNames.every(name => {
|
||||
const processed = name.toLowerCase().replace(/\s+/g, '');
|
||||
return testNames.every((name) => {
|
||||
const processed = name.toLowerCase().replace(/\s+/g, "");
|
||||
return processed.length > 0;
|
||||
});
|
||||
} catch (error) {
|
||||
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
|
||||
@@ -285,4 +309,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
runner.runAll().catch(console.error);
|
||||
}
|
||||
|
||||
export default runner;
|
||||
export default runner;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,71 +7,72 @@
|
||||
* Usage: node tests/index.js [--unit] [--integration] [--manual]
|
||||
*/
|
||||
|
||||
import cliTestRunner from './cli.test.js';
|
||||
import integrationTestRunner from './integration.test.js';
|
||||
import builderTestRunner from './builder.test.js';
|
||||
import { execSync } from 'child_process';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import ora from 'ora';
|
||||
import cliTestRunner from "./cli.test.js";
|
||||
import integrationTestRunner from "./integration.test.js";
|
||||
import builderTestRunner from "./builder.test.js";
|
||||
import { execSync } from "child_process";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import ora from "ora";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
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 runUnit = args.length === 0 || args.includes('--unit');
|
||||
const runIntegration = args.length === 0 || args.includes('--integration');
|
||||
const runBuilder = args.length === 0 || args.includes('--builder');
|
||||
const runUnit = args.length === 0 || args.includes("--unit");
|
||||
const runIntegration = args.length === 0 || args.includes("--integration");
|
||||
const runBuilder = args.length === 0 || args.includes("--builder");
|
||||
|
||||
async function runAllTests() {
|
||||
console.log('🚀 Pake CLI Test Suite');
|
||||
console.log('=======================\n');
|
||||
console.log("🚀 Pake CLI Test Suite");
|
||||
console.log("=======================\n");
|
||||
|
||||
let totalPassed = 0;
|
||||
let totalTests = 0;
|
||||
|
||||
if (runUnit) {
|
||||
console.log('📋 Running Unit Tests...\n');
|
||||
console.log("📋 Running Unit Tests...\n");
|
||||
await cliTestRunner.runAll();
|
||||
totalPassed += cliTestRunner.results.filter(r => r.passed).length;
|
||||
totalPassed += cliTestRunner.results.filter((r) => r.passed).length;
|
||||
totalTests += cliTestRunner.results.length;
|
||||
console.log('');
|
||||
console.log("");
|
||||
}
|
||||
|
||||
if (runIntegration) {
|
||||
console.log('🔧 Running Integration Tests...\n');
|
||||
console.log("🔧 Running Integration Tests...\n");
|
||||
await integrationTestRunner.runAll();
|
||||
totalPassed += integrationTestRunner.results.filter(r => r.passed).length;
|
||||
totalPassed += integrationTestRunner.results.filter((r) => r.passed).length;
|
||||
totalTests += integrationTestRunner.results.length;
|
||||
console.log('');
|
||||
console.log("");
|
||||
}
|
||||
|
||||
if (runBuilder) {
|
||||
console.log('🏗️ Running Builder Tests...\n');
|
||||
console.log("🏗️ Running Builder Tests...\n");
|
||||
await builderTestRunner.runAll();
|
||||
totalPassed += builderTestRunner.results.filter(r => r.passed).length;
|
||||
totalPassed += builderTestRunner.results.filter((r) => r.passed).length;
|
||||
totalTests += builderTestRunner.results.length;
|
||||
console.log('');
|
||||
console.log("");
|
||||
}
|
||||
|
||||
|
||||
// Final summary
|
||||
console.log('🎯 Overall Test Summary');
|
||||
console.log('=======================');
|
||||
console.log("🎯 Overall Test Summary");
|
||||
console.log("=======================");
|
||||
console.log(`Total: ${totalPassed}/${totalTests} tests passed`);
|
||||
|
||||
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 {
|
||||
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
|
||||
process.exit(totalPassed === totalTests ? 0 : 1);
|
||||
}
|
||||
|
||||
runAllTests().catch(error => {
|
||||
console.error('❌ Test runner failed:', error);
|
||||
runAllTests().catch((error) => {
|
||||
console.error("❌ Test runner failed:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
/**
|
||||
* Integration Tests for Pake CLI
|
||||
*
|
||||
*
|
||||
* These tests verify that different components work together correctly.
|
||||
* They may take longer to run as they test actual build processes.
|
||||
*/
|
||||
|
||||
import { spawn, execSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import config, { TIMEOUTS, TEST_URLS } from './test.config.js';
|
||||
import ora from 'ora';
|
||||
import { spawn, execSync } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import config, { TIMEOUTS, TEST_URLS } from "./test.config.js";
|
||||
import ora from "ora";
|
||||
|
||||
class IntegrationTestRunner {
|
||||
constructor() {
|
||||
@@ -25,20 +25,20 @@ class IntegrationTestRunner {
|
||||
}
|
||||
|
||||
async runAll() {
|
||||
console.log('🔧 Integration Tests');
|
||||
console.log('====================\n');
|
||||
console.log("🔧 Integration Tests");
|
||||
console.log("====================\n");
|
||||
|
||||
for (const [index, test] of this.tests.entries()) {
|
||||
const spinner = ora(`Running ${test.name}...`).start();
|
||||
|
||||
|
||||
try {
|
||||
const result = await Promise.race([
|
||||
test.testFn(),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout')), test.timeout)
|
||||
)
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error("Timeout")), test.timeout),
|
||||
),
|
||||
]);
|
||||
|
||||
|
||||
if (result) {
|
||||
spinner.succeed(`${index + 1}. ${test.name}: PASS`);
|
||||
this.results.push({ name: test.name, passed: true });
|
||||
@@ -47,8 +47,14 @@ class IntegrationTestRunner {
|
||||
this.results.push({ name: test.name, passed: false });
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail(`${index + 1}. ${test.name}: ERROR - ${error.message.slice(0, 50)}...`);
|
||||
this.results.push({ name: test.name, passed: false, error: error.message });
|
||||
spinner.fail(
|
||||
`${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() {
|
||||
// Clean up any temporary files created during tests
|
||||
this.tempFiles.forEach(file => {
|
||||
this.tempFiles.forEach((file) => {
|
||||
try {
|
||||
if (fs.existsSync(file)) {
|
||||
fs.unlinkSync(file);
|
||||
@@ -70,18 +76,22 @@ class IntegrationTestRunner {
|
||||
}
|
||||
|
||||
displayResults() {
|
||||
const passed = this.results.filter(r => r.passed).length;
|
||||
const passed = this.results.filter((r) => r.passed).length;
|
||||
const total = this.results.length;
|
||||
|
||||
console.log(`\n📊 Integration Test Results: ${passed}/${total} passed\n`);
|
||||
|
||||
|
||||
if (passed === total) {
|
||||
console.log('🎉 All integration tests passed!');
|
||||
console.log("🎉 All integration tests passed!");
|
||||
} else {
|
||||
console.log('❌ Some integration tests failed');
|
||||
this.results.filter(r => !r.passed).forEach(result => {
|
||||
console.log(` - ${result.name}${result.error ? `: ${result.error}` : ''}`);
|
||||
});
|
||||
console.log("❌ Some integration tests failed");
|
||||
this.results
|
||||
.filter((r) => !r.passed)
|
||||
.forEach((result) => {
|
||||
console.log(
|
||||
` - ${result.name}${result.error ? `: ${result.error}` : ""}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,60 +103,57 @@ class IntegrationTestRunner {
|
||||
const runner = new IntegrationTestRunner();
|
||||
|
||||
// Integration Tests
|
||||
runner.addTest(
|
||||
'CLI Process Spawning',
|
||||
() => {
|
||||
return new Promise((resolve) => {
|
||||
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);
|
||||
runner.addTest("CLI Process Spawning", () => {
|
||||
return new Promise((resolve) => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
runner.addTest(
|
||||
'Interactive Mode Simulation',
|
||||
"Interactive Mode Simulation",
|
||||
() => {
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn('node', [config.CLI_PATH, TEST_URLS.WEEKLY], {
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
const child = spawn("node", [config.CLI_PATH, TEST_URLS.WEEKLY], {
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
let output = '';
|
||||
|
||||
let output = "";
|
||||
let prompted = false;
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
|
||||
child.stdout.on("data", (data) => {
|
||||
output += data.toString();
|
||||
// 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;
|
||||
child.stdin.write('TestApp\n');
|
||||
child.stdin.write("TestApp\n");
|
||||
setTimeout(() => {
|
||||
child.kill();
|
||||
resolve(true);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
child.on('close', () => {
|
||||
|
||||
child.on("close", () => {
|
||||
resolve(prompted);
|
||||
});
|
||||
|
||||
|
||||
// Timeout after 10 seconds
|
||||
setTimeout(() => {
|
||||
child.kill();
|
||||
@@ -154,86 +161,80 @@ runner.addTest(
|
||||
}, 10000);
|
||||
});
|
||||
},
|
||||
TIMEOUTS.MEDIUM
|
||||
TIMEOUTS.MEDIUM,
|
||||
);
|
||||
|
||||
runner.addTest(
|
||||
'Command Line Argument Parsing',
|
||||
"Command Line Argument Parsing",
|
||||
() => {
|
||||
try {
|
||||
// Test argument validation by running CLI with --help to verify args are parsed
|
||||
const helpOutput = execSync(`node "${config.CLI_PATH}" --help`, {
|
||||
encoding: 'utf8',
|
||||
timeout: 3000
|
||||
const helpOutput = execSync(`node "${config.CLI_PATH}" --help`, {
|
||||
encoding: "utf8",
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
|
||||
// Verify that our command structure is valid by checking help includes our options
|
||||
const validOptions = ['--width', '--height', '--debug', '--name'].every(opt =>
|
||||
helpOutput.includes(opt)
|
||||
const validOptions = ["--width", "--height", "--debug", "--name"].every(
|
||||
(opt) => helpOutput.includes(opt),
|
||||
);
|
||||
|
||||
|
||||
return validOptions;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
TIMEOUTS.QUICK
|
||||
TIMEOUTS.QUICK,
|
||||
);
|
||||
|
||||
runner.addTest(
|
||||
'File System Permissions',
|
||||
() => {
|
||||
try {
|
||||
// Test that we can write to current directory
|
||||
const testFile = 'test-write-permission.tmp';
|
||||
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("File System Permissions", () => {
|
||||
try {
|
||||
// Test that we can write to current directory
|
||||
const testFile = "test-write-permission.tmp";
|
||||
fs.writeFileSync(testFile, "test");
|
||||
runner.trackTempFile(testFile);
|
||||
|
||||
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;
|
||||
}
|
||||
// Test that we can read from CLI directory
|
||||
const cliStats = fs.statSync(config.CLI_PATH);
|
||||
return cliStats.isFile();
|
||||
} catch (error) {
|
||||
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
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
runner.runAll().catch(console.error);
|
||||
}
|
||||
|
||||
export default runner;
|
||||
export default runner;
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
/**
|
||||
* Test Configuration for Pake CLI
|
||||
*
|
||||
*
|
||||
* This file contains test configuration and utilities
|
||||
* shared across different test files.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
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)
|
||||
export const TIMEOUTS = {
|
||||
QUICK: 3000, // For version, help commands
|
||||
MEDIUM: 10000, // For validation tests
|
||||
LONG: 300000, // For build tests (5 minutes)
|
||||
QUICK: 3000, // For version, help commands
|
||||
MEDIUM: 10000, // For validation tests
|
||||
LONG: 300000, // For build tests (5 minutes)
|
||||
};
|
||||
|
||||
// Test URLs for different scenarios
|
||||
export const TEST_URLS = {
|
||||
WEEKLY: 'https://weekly.tw93.fun',
|
||||
VALID: 'https://example.com',
|
||||
GITHUB: 'https://github.com',
|
||||
GOOGLE: 'https://www.google.com',
|
||||
INVALID: 'not://a/valid[url]',
|
||||
LOCAL: './test-file.html'
|
||||
WEEKLY: "https://weekly.tw93.fun",
|
||||
VALID: "https://example.com",
|
||||
GITHUB: "https://github.com",
|
||||
GOOGLE: "https://www.google.com",
|
||||
INVALID: "not://a/valid[url]",
|
||||
LOCAL: "./test-file.html",
|
||||
};
|
||||
|
||||
// Test assets for different scenarios
|
||||
export const TEST_ASSETS = {
|
||||
WEEKLY_ICNS: 'https://gw.alipayobjects.com/os/k/fw/weekly.icns',
|
||||
INVALID_ICON: 'https://example.com/nonexistent.icns'
|
||||
WEEKLY_ICNS: "https://gw.alipayobjects.com/os/k/fw/weekly.icns",
|
||||
INVALID_ICON: "https://example.com/nonexistent.icns",
|
||||
};
|
||||
|
||||
// Test app names
|
||||
export const TEST_NAMES = {
|
||||
WEEKLY: 'Weekly',
|
||||
BASIC: 'TestApp',
|
||||
DEBUG: 'DebugApp',
|
||||
FULL: 'FullscreenApp',
|
||||
GOOGLE_TRANSLATE: 'Google Translate',
|
||||
MAC: 'MacApp'
|
||||
WEEKLY: "Weekly",
|
||||
BASIC: "TestApp",
|
||||
DEBUG: "DebugApp",
|
||||
FULL: "FullscreenApp",
|
||||
GOOGLE_TRANSLATE: "Google Translate",
|
||||
MAC: "MacApp",
|
||||
};
|
||||
|
||||
// Expected file extensions by platform
|
||||
export const PLATFORM_EXTENSIONS = {
|
||||
darwin: 'dmg',
|
||||
win32: 'msi',
|
||||
linux: 'deb'
|
||||
darwin: "dmg",
|
||||
win32: "msi",
|
||||
linux: "deb",
|
||||
};
|
||||
|
||||
// Helper functions
|
||||
@@ -57,16 +57,16 @@ export const testHelpers = {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
getExpectedOutput: (appName) => {
|
||||
const ext = PLATFORM_EXTENSIONS[process.platform] || 'bin';
|
||||
const ext = PLATFORM_EXTENSIONS[process.platform] || "bin";
|
||||
return `${appName}.${ext}`;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Create test command with common options
|
||||
*/
|
||||
@@ -75,14 +75,14 @@ export const testHelpers = {
|
||||
const optionsStr = Object.entries(options)
|
||||
.map(([key, value]) => {
|
||||
if (value === true) return `--${key}`;
|
||||
if (value === false) return '';
|
||||
if (value === false) return "";
|
||||
return `--${key} "${value}"`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
.join(" ");
|
||||
|
||||
return `${baseCmd} ${optionsStr}`.trim();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
@@ -92,5 +92,5 @@ export default {
|
||||
TEST_URLS,
|
||||
TEST_NAMES,
|
||||
PLATFORM_EXTENSIONS,
|
||||
testHelpers
|
||||
};
|
||||
testHelpers,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user