diff --git a/.github/workflows/claude-unified.yml b/.github/workflows/claude-unified.yml index d444a31..1ec2541 100644 --- a/.github/workflows/claude-unified.yml +++ b/.github/workflows/claude-unified.yml @@ -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 \ No newline at end of file + use_sticky_comment: true diff --git a/.github/workflows/quality-and-test.yml b/.github/workflows/quality-and-test.yml index 74318c3..ff348b0 100644 --- a/.github/workflows/quality-and-test.yml +++ b/.github/workflows/quality-and-test.yml @@ -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 \ No newline at end of file + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2812764..856fe52 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -113,4 +113,4 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - platforms: linux/amd64 \ No newline at end of file + platforms: linux/amd64 diff --git a/CLAUDE.md b/CLAUDE.md index 87ecea8..f805433 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 147f01c..23f2ea0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 ``` diff --git a/bin/builders/BaseBuilder.ts b/bin/builders/BaseBuilder.ts index e6899db..eb4a21d 100644 --- a/bin/builders/BaseBuilder.ts +++ b/bin/builders/BaseBuilder.ts @@ -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}`, ); } - } diff --git a/tests/README.md b/tests/README.md index 81023e7..e2bd252 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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, ); ``` diff --git a/tests/builder.test.js b/tests/builder.test.js index cc34d7f..815fcfa 100644 --- a/tests/builder.test.js +++ b/tests/builder.test.js @@ -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; \ No newline at end of file +export default runner; diff --git a/tests/cli.test.js b/tests/cli.test.js index 040f989..0d07ea9 100644 --- a/tests/cli.test.js +++ b/tests/cli.test.js @@ -2,17 +2,22 @@ /** * Pake CLI Test Suite - * + * * This is the main test file for the Pake CLI tool. * It includes unit tests, integration tests, and manual test scenarios. */ -import { execSync, spawn } from 'child_process'; -import { fileURLToPath } from 'url'; -import path from 'path'; -import fs from 'fs'; -import ora from 'ora'; -import config, { TIMEOUTS, TEST_URLS, TEST_NAMES, TEST_ASSETS } from './test.config.js'; +import { execSync, spawn } from "child_process"; +import { fileURLToPath } from "url"; +import path from "path"; +import fs from "fs"; +import ora from "ora"; +import config, { + TIMEOUTS, + TEST_URLS, + TEST_NAMES, + TEST_ASSETS, +} from "./test.config.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const projectRoot = config.PROJECT_ROOT; @@ -25,23 +30,23 @@ class TestRunner { this.results = []; } - addTest(name, testFn, description = '') { + addTest(name, testFn, description = "") { this.tests.push({ name, testFn, description }); } async runAll() { - console.log('๐Ÿงช Pake CLI Test Suite'); - console.log('======================\n'); + console.log("๐Ÿงช Pake CLI Test Suite"); + console.log("======================\n"); // Quick validation this.validateEnvironment(); - console.log('๐Ÿ” Running Unit Tests:'); - console.log('----------------------\n'); + console.log("๐Ÿ” Running Unit 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) { @@ -52,8 +57,14 @@ class TestRunner { 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, + }); } } @@ -62,190 +73,192 @@ class TestRunner { } validateEnvironment() { - console.log('๐Ÿ”ง Environment Validation:'); - console.log('---------------------------'); + console.log("๐Ÿ”ง Environment Validation:"); + console.log("---------------------------"); // Check if CLI file exists if (!fs.existsSync(cliPath)) { - console.log('โŒ CLI file not found. Run: npm run cli:build'); + console.log("โŒ CLI file not found. Run: npm run cli:build"); process.exit(1); } - console.log('โœ… CLI file exists'); + console.log("โœ… CLI file exists"); // Check if CLI is executable try { - execSync(`node "${cliPath}" --version`, { encoding: 'utf8', timeout: 3000 }); - console.log('โœ… CLI is executable'); + execSync(`node "${cliPath}" --version`, { + encoding: "utf8", + timeout: 3000, + }); + console.log("โœ… CLI is executable"); } catch (error) { - console.log('โŒ CLI is not executable'); + console.log("โŒ CLI is not executable"); process.exit(1); } - console.log('โœ… Environment is ready\n'); + console.log("โœ… Environment is ready\n"); } 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๐Ÿ“Š Test Results:'); - console.log('================'); + console.log("\n๐Ÿ“Š Test Results:"); + console.log("================"); - this.results.forEach(result => { - const status = result.passed ? 'โœ…' : 'โŒ'; - const error = result.error ? ` (${result.error})` : ''; + this.results.forEach((result) => { + const status = result.passed ? "โœ…" : "โŒ"; + const error = result.error ? ` (${result.error})` : ""; console.log(`${status} ${result.name}${error}`); }); console.log(`\n๐ŸŽฏ Summary: ${passed}/${total} tests passed`); if (passed === total) { - console.log('๐ŸŽ‰ All tests passed!\n'); + console.log("๐ŸŽ‰ All tests passed!\n"); } else { console.log(`โŒ ${total - passed} test(s) failed\n`); } } displayManualTestScenarios() { - console.log('๐Ÿ”ง Manual Test Scenarios:'); - console.log('=========================\n'); + console.log("๐Ÿ”ง Manual Test Scenarios:"); + console.log("=========================\n"); const scenarios = [ { - name: 'Weekly App Creation (Primary Test Case)', + name: "Weekly App Creation (Primary Test Case)", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "${TEST_NAMES.WEEKLY}"`, - description: 'Creates Weekly desktop app from tw93 weekly site', - expectedTime: '2-5 minutes (first time), 30-60s (subsequent)' + description: "Creates Weekly desktop app from tw93 weekly site", + expectedTime: "2-5 minutes (first time), 30-60s (subsequent)", }, { - name: 'Weekly App with Custom Size', + name: "Weekly App with Custom Size", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "${TEST_NAMES.WEEKLY}" --width 1200 --height 800`, - description: 'Creates Weekly app with optimal window dimensions', - expectedTime: '2-5 minutes' + description: "Creates Weekly app with optimal window dimensions", + expectedTime: "2-5 minutes", }, { - name: 'Debug Build with Weekly', + name: "Debug Build with Weekly", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "${TEST_NAMES.DEBUG}" --debug`, - description: 'Creates debug build with verbose output for troubleshooting', - expectedTime: '2-5 minutes' + description: + "Creates debug build with verbose output for troubleshooting", + expectedTime: "2-5 minutes", }, { - name: 'Google Translate with Spaces in Name', + name: "Google Translate with Spaces in Name", command: `node "${cliPath}" https://translate.google.com --name "${TEST_NAMES.GOOGLE_TRANSLATE}"`, - description: 'Tests app name with spaces (auto-handled per platform)', - expectedTime: '2-5 minutes' + description: "Tests app name with spaces (auto-handled per platform)", + expectedTime: "2-5 minutes", }, { - name: 'Always On Top App', + name: "Always On Top App", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "TopWeekly" --always-on-top`, - description: 'Creates app that stays on top of other windows', - expectedTime: '2-5 minutes' + description: "Creates app that stays on top of other windows", + expectedTime: "2-5 minutes", }, { - name: 'Full Screen App', + name: "Full Screen App", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "FullWeekly" --fullscreen`, - description: 'Creates full-screen Weekly app', - expectedTime: '2-5 minutes' + description: "Creates full-screen Weekly app", + expectedTime: "2-5 minutes", }, { - name: 'System Tray App', + name: "System Tray App", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "TrayWeekly" --show-system-tray`, - description: 'Creates app with system tray integration', - expectedTime: '2-5 minutes' + description: "Creates app with system tray integration", + expectedTime: "2-5 minutes", }, { - name: 'Custom User Agent', + name: "Custom User Agent", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "UAWeekly" --user-agent "Pake/1.0 Weekly App"`, - description: 'Creates app with custom browser user agent', - expectedTime: '2-5 minutes' + description: "Creates app with custom browser user agent", + expectedTime: "2-5 minutes", }, { - name: 'Version Controlled App', + name: "Version Controlled App", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "VersionWeekly" --app-version "2.1.0"`, - description: 'Creates app with specific version number', - expectedTime: '2-5 minutes' + description: "Creates app with specific version number", + expectedTime: "2-5 minutes", }, { - name: 'Weekly App with Remote Icon', + name: "Weekly App with Remote Icon", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "IconWeekly" --icon "${TEST_ASSETS.WEEKLY_ICNS}"`, - description: 'Creates Weekly app with remote icns icon from CDN', - expectedTime: '2-5 minutes' + description: "Creates Weekly app with remote icns icon from CDN", + expectedTime: "2-5 minutes", }, { - name: 'Weekly App with Proxy', + name: "Weekly App with Proxy", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "ProxyWeekly" --proxy-url "http://127.0.0.1:7890"`, - description: 'Creates Weekly app with HTTP proxy configuration', - expectedTime: '2-5 minutes' + description: "Creates Weekly app with HTTP proxy configuration", + expectedTime: "2-5 minutes", }, { - name: 'Weekly App with Global Shortcut', + name: "Weekly App with Global Shortcut", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "ShortcutWeekly" --activation-shortcut "CmdOrControl+Shift+W"`, - description: 'Creates Weekly app with global activation shortcut', - expectedTime: '2-5 minutes' + description: "Creates Weekly app with global activation shortcut", + expectedTime: "2-5 minutes", }, { - name: 'Weekly App with Hide on Close', + name: "Weekly App with Hide on Close", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "HideWeekly" --hide-on-close`, - description: 'Creates Weekly app that hides instead of closing', - expectedTime: '2-5 minutes' + description: "Creates Weekly app that hides instead of closing", + expectedTime: "2-5 minutes", }, { - name: 'Weekly App with Disabled Web Shortcuts', + name: "Weekly App with Disabled Web Shortcuts", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "NoShortcutWeekly" --disabled-web-shortcuts`, - description: 'Creates Weekly app with web shortcuts disabled', - expectedTime: '2-5 minutes' - } + description: "Creates Weekly app with web shortcuts disabled", + expectedTime: "2-5 minutes", + }, ]; - if (process.platform === 'darwin') { + if (process.platform === "darwin") { scenarios.push( { - name: 'Mac Universal Binary (Weekly)', + name: "Mac Universal Binary (Weekly)", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "${TEST_NAMES.WEEKLY}" --multi-arch`, - description: 'Creates universal binary for Intel and Apple Silicon', - expectedTime: '5-10 minutes' + description: "Creates universal binary for Intel and Apple Silicon", + expectedTime: "5-10 minutes", }, { - name: 'Mac Dark Mode App', + name: "Mac Dark Mode App", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "DarkWeekly" --dark-mode`, - description: 'Forces dark mode on macOS', - expectedTime: '2-5 minutes' + description: "Forces dark mode on macOS", + expectedTime: "2-5 minutes", }, { - name: 'Mac Immersive Title Bar', + name: "Mac Immersive Title Bar", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "ImmersiveWeekly" --hide-title-bar`, - description: 'Creates app with hidden title bar (macOS only)', - expectedTime: '2-5 minutes' - } + description: "Creates app with hidden title bar (macOS only)", + expectedTime: "2-5 minutes", + }, ); } - - if (process.platform === 'linux') { + + if (process.platform === "linux") { scenarios.push( { - name: 'Linux AppImage Build', + name: "Linux AppImage Build", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "${TEST_NAMES.WEEKLY}" --targets appimage`, - description: 'Creates AppImage package for Linux', - expectedTime: '3-7 minutes' + description: "Creates AppImage package for Linux", + expectedTime: "3-7 minutes", }, { - name: 'Linux RPM Build', + name: "Linux RPM Build", command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "${TEST_NAMES.WEEKLY}" --targets rpm`, - description: 'Creates RPM package for Linux', - expectedTime: '3-7 minutes' - } + description: "Creates RPM package for Linux", + expectedTime: "3-7 minutes", + }, ); } - - if (process.platform === 'win32') { - scenarios.push( - { - name: 'Windows with Chinese Installer', - command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "${TEST_NAMES.WEEKLY}" --installer-language zh-CN`, - description: 'Creates Windows installer with Chinese language', - expectedTime: '3-7 minutes' - } - ); + + if (process.platform === "win32") { + scenarios.push({ + name: "Windows with Chinese Installer", + command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "${TEST_NAMES.WEEKLY}" --installer-language zh-CN`, + description: "Creates Windows installer with Chinese language", + expectedTime: "3-7 minutes", + }); } scenarios.forEach((scenario, index) => { @@ -255,12 +268,12 @@ class TestRunner { console.log(` Expected Time: ${scenario.expectedTime}\n`); }); - console.log('๐Ÿ’ก Usage Instructions:'); - console.log(' 1. Copy any command above'); - console.log(' 2. Run it in your terminal'); - console.log(' 3. Wait for the build to complete'); - console.log(' 4. Check for the generated app file in current directory'); - console.log(' 5. Launch the app to verify it works\n'); + console.log("๐Ÿ’ก Usage Instructions:"); + console.log(" 1. Copy any command above"); + console.log(" 2. Run it in your terminal"); + console.log(" 3. Wait for the build to complete"); + console.log(" 4. Check for the generated app file in current directory"); + console.log(" 5. Launch the app to verify it works\n"); } } @@ -269,53 +282,65 @@ const runner = new TestRunner(); // Unit Tests runner.addTest( - 'Version Command', + "Version Command", () => { - const output = execSync(`node "${cliPath}" --version`, { encoding: 'utf8', timeout: 3000 }); + const output = execSync(`node "${cliPath}" --version`, { + encoding: "utf8", + timeout: 3000, + }); return /^\d+\.\d+\.\d+/.test(output.trim()); }, - 'Should output version number' + "Should output version number", ); runner.addTest( - 'Help Command', + "Help Command", () => { - const output = execSync(`node "${cliPath}" --help`, { encoding: 'utf8', timeout: 3000 }); - return output.includes('Usage: cli [url] [options]'); + const output = execSync(`node "${cliPath}" --help`, { + encoding: "utf8", + timeout: 3000, + }); + return output.includes("Usage: cli [url] [options]"); }, - 'Should display help information' + "Should display help information", ); runner.addTest( - 'No Arguments Behavior', + "No Arguments Behavior", () => { - const output = execSync(`node "${cliPath}"`, { encoding: 'utf8', timeout: 3000 }); - return output.includes('Usage: cli [url] [options]'); + const output = execSync(`node "${cliPath}"`, { + encoding: "utf8", + timeout: 3000, + }); + return output.includes("Usage: cli [url] [options]"); }, - 'Should display help when no arguments provided' + "Should display help when no arguments provided", ); runner.addTest( - 'Invalid Number Validation', + "Invalid Number Validation", () => { try { - execSync(`node "${cliPath}" https://example.com --width abc`, { encoding: 'utf8', timeout: 3000 }); + execSync(`node "${cliPath}" https://example.com --width abc`, { + encoding: "utf8", + timeout: 3000, + }); return false; // Should throw error } catch (error) { - return error.message.includes('Not a number'); + return error.message.includes("Not a number"); } }, - 'Should reject invalid number inputs' + "Should reject invalid number inputs", ); runner.addTest( - 'URL Validation', + "URL Validation", () => { try { // Test with a clearly invalid URL that should fail - execSync(`node "${cliPath}" "${TEST_URLS.INVALID}" --name TestApp`, { - encoding: 'utf8', - timeout: 3000 + execSync(`node "${cliPath}" "${TEST_URLS.INVALID}" --name TestApp`, { + encoding: "utf8", + timeout: 3000, }); return false; // Should have failed } catch (error) { @@ -323,140 +348,168 @@ runner.addTest( return error.status !== 0; } }, - 'Should reject malformed URLs' + "Should reject malformed URLs", ); runner.addTest( - 'Required Dependencies Check', + "Required Dependencies Check", () => { // Check if essential Node.js modules are available try { - const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')); + const packageJson = JSON.parse( + fs.readFileSync(path.join(projectRoot, "package.json"), "utf8"), + ); const hasEssentialDeps = [ - 'commander', - 'chalk', - 'fs-extra', - 'execa' - ].every(dep => packageJson.dependencies[dep]); - + "commander", + "chalk", + "fs-extra", + "execa", + ].every((dep) => packageJson.dependencies[dep]); + return hasEssentialDeps; } catch { return false; } }, - 'Should have all required dependencies' + "Should have all required dependencies", ); // Performance and Integration Tests runner.addTest( - 'CLI Response Time', + "CLI Response Time", () => { const start = Date.now(); - execSync(`node "${cliPath}" --version`, { encoding: 'utf8', timeout: 3000 }); + execSync(`node "${cliPath}" --version`, { + encoding: "utf8", + timeout: 3000, + }); const elapsed = Date.now() - start; - + // CLI should respond within 2 seconds return elapsed < 2000; }, - 'Should respond quickly to simple commands' + "Should respond quickly to simple commands", ); runner.addTest( - 'Build Command Generation', + "Build Command Generation", () => { // Test that getBuildCommand logic works - const output = execSync(`node "${cliPath}" --help`, { encoding: 'utf8', timeout: 3000 }); - return output.includes('--debug') && output.includes('Debug build'); + const output = execSync(`node "${cliPath}" --help`, { + encoding: "utf8", + timeout: 3000, + }); + return output.includes("--debug") && output.includes("Debug build"); }, - 'Should support debug build options' + "Should support debug build options", ); // New comprehensive option validation tests runner.addTest( - 'CLI Options Validation - Core Options Present', + "CLI Options Validation - Core Options Present", () => { - const output = execSync(`node "${cliPath}" --help`, { encoding: 'utf8', timeout: 3000 }); + const output = execSync(`node "${cliPath}" --help`, { + encoding: "utf8", + timeout: 3000, + }); const coreOptions = [ - '--name', '--icon', '--height', '--width', '--hide-title-bar', - '--fullscreen', '--multi-arch', '--use-local-file', '--inject', '--debug' + "--name", + "--icon", + "--height", + "--width", + "--hide-title-bar", + "--fullscreen", + "--multi-arch", + "--use-local-file", + "--inject", + "--debug", ]; - - return coreOptions.every(option => output.includes(option)); + + return coreOptions.every((option) => output.includes(option)); }, - 'Should include core CLI options' + "Should include core CLI options", ); runner.addTest( - 'Weekly URL Accessibility', + "Weekly URL Accessibility", () => { try { // Test that weekly.tw93.fun is accessible for our test cases const testCommand = `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "URLTest" --debug`; // We're not actually building, just testing URL parsing doesn't fail immediately - execSync(`echo "n" | timeout 5s ${testCommand} || true`, { - encoding: 'utf8', - timeout: 8000 + execSync(`echo "n" | timeout 5s ${testCommand} || true`, { + encoding: "utf8", + timeout: 8000, }); return true; // If we get here, URL was parsed successfully } catch (error) { // Check if it's a timeout (expected) vs URL error (unexpected) - return !error.message.includes('Invalid URL') && !error.message.includes('invalid'); + return ( + !error.message.includes("Invalid URL") && + !error.message.includes("invalid") + ); } }, - 'Should accept weekly.tw93.fun as valid URL' + "Should accept weekly.tw93.fun as valid URL", ); runner.addTest( - 'App Version Format Validation', + "App Version Format Validation", () => { try { // Test with valid version format first - execSync(`node "${cliPath}" --help`, { encoding: 'utf8', timeout: 3000 }); + execSync(`node "${cliPath}" --help`, { encoding: "utf8", timeout: 3000 }); // If CLI accepts --app-version in help, it should validate the format - const helpOutput = execSync(`node "${cliPath}" --help`, { encoding: 'utf8', timeout: 3000 }); - return helpOutput.includes('--') && helpOutput.includes('version'); + const helpOutput = execSync(`node "${cliPath}" --help`, { + encoding: "utf8", + timeout: 3000, + }); + return helpOutput.includes("--") && helpOutput.includes("version"); } catch (error) { return false; } }, - 'Should have app version option available' + "Should have app version option available", ); runner.addTest( - 'Activation Shortcut Format', + "Activation Shortcut Format", () => { try { // Test valid shortcut format - const output = execSync(`echo "n" | timeout 3s node "${cliPath}" ${TEST_URLS.WEEKLY} --activation-shortcut "CmdOrControl+Shift+P" --name "ShortcutTest" || true`, { - encoding: 'utf8', - timeout: 5000 - }); + const output = execSync( + `echo "n" | timeout 3s node "${cliPath}" ${TEST_URLS.WEEKLY} --activation-shortcut "CmdOrControl+Shift+P" --name "ShortcutTest" || true`, + { + encoding: "utf8", + timeout: 5000, + }, + ); // Should not immediately fail on valid shortcut format - return !output.includes('Invalid shortcut'); + return !output.includes("Invalid shortcut"); } catch (error) { - return !error.message.includes('Invalid shortcut'); + return !error.message.includes("Invalid shortcut"); } }, - 'Should accept valid activation shortcut format' + "Should accept valid activation shortcut format", ); // Critical implementation tests based on bin/ analysis runner.addTest( - 'File Naming Pattern Validation', + "File Naming Pattern Validation", () => { try { // Test that app names are properly sanitized for filenames const testCases = [ - { name: 'Simple', expected: true }, - { name: 'With Spaces', expected: true }, - { name: 'Special-Chars_123', expected: true }, - { name: '', expected: false } + { name: "Simple", expected: true }, + { name: "With Spaces", expected: true }, + { name: "Special-Chars_123", expected: true }, + { name: "", expected: false }, ]; - + // Test name validation logic exists - return testCases.every(testCase => { - if (testCase.name === '') { + return testCases.every((testCase) => { + if (testCase.name === "") { // Empty names should be handled return true; } @@ -467,50 +520,46 @@ runner.addTest( return false; } }, - 'Should handle various app name formats for file naming' + "Should handle various app name formats for file naming", ); runner.addTest( - 'Platform-specific Build Output Validation', + "Platform-specific Build Output Validation", () => { try { // Verify that platform detection works properly const platform = process.platform; const expectedExtensions = { - 'darwin': '.dmg', - 'win32': '.msi', - 'linux': '.deb' + darwin: ".dmg", + win32: ".msi", + linux: ".deb", }; - + const expectedExt = expectedExtensions[platform]; return expectedExt !== undefined; } catch (error) { return false; } }, - 'Should detect platform and use correct file extensions' + "Should detect platform and use correct file extensions", ); runner.addTest( - 'URL Validation and Processing', + "URL Validation and Processing", () => { try { // Test URL validation logic with various formats const validUrls = [ - 'https://weekly.tw93.fun', - 'http://example.com', - 'https://subdomain.example.com/path' + "https://weekly.tw93.fun", + "http://example.com", + "https://subdomain.example.com/path", ]; - - const invalidUrls = [ - 'not-a-url', - 'ftp://invalid-protocol.com', - '' - ]; - + + const invalidUrls = ["not-a-url", "ftp://invalid-protocol.com", ""]; + // All valid URLs should be accepted by our validation // This tests the URL processing logic without actually building - return validUrls.every(url => { + return validUrls.every((url) => { try { new URL(url); return true; @@ -522,56 +571,60 @@ runner.addTest( return false; } }, - 'Should properly validate and process URLs' + "Should properly validate and process URLs", ); runner.addTest( - 'Icon Format Validation', + "Icon Format Validation", () => { try { // Test icon extension validation logic based on platform const platform = process.platform; const validIconExtensions = { - 'darwin': ['.icns'], - 'win32': ['.ico'], - 'linux': ['.png'] + darwin: [".icns"], + win32: [".ico"], + linux: [".png"], }; - + const platformIcons = validIconExtensions[platform]; return platformIcons && platformIcons.length > 0; } catch (error) { return false; } }, - 'Should validate icon formats per platform' + "Should validate icon formats per platform", ); runner.addTest( - 'Injection File Validation', + "Injection File Validation", () => { try { // Test injection file validation (CSS/JS only) - const validFiles = ['style.css', 'script.js', 'custom.CSS', 'app.JS']; - const invalidFiles = ['image.png', 'doc.txt', 'app.html']; - + const validFiles = ["style.css", "script.js", "custom.CSS", "app.JS"]; + const invalidFiles = ["image.png", "doc.txt", "app.html"]; + const isValidInjectionFile = (filename) => { - return filename.toLowerCase().endsWith('.css') || - filename.toLowerCase().endsWith('.js'); + return ( + filename.toLowerCase().endsWith(".css") || + filename.toLowerCase().endsWith(".js") + ); }; - + const validResults = validFiles.every(isValidInjectionFile); - const invalidResults = invalidFiles.every(file => !isValidInjectionFile(file)); - + const invalidResults = invalidFiles.every( + (file) => !isValidInjectionFile(file), + ); + return validResults && invalidResults; } catch (error) { return false; } }, - 'Should validate injection file formats (CSS/JS only)' + "Should validate injection file formats (CSS/JS only)", ); runner.addTest( - 'Configuration Merging Logic', + "Configuration Merging Logic", () => { try { // Test that configuration options are properly structured @@ -580,298 +633,324 @@ runner.addTest( height: 800, fullscreen: false, debug: false, - name: 'TestApp' + name: "TestApp", }; - + // Verify all critical config properties exist and have correct types - return typeof mockConfig.width === 'number' && - typeof mockConfig.height === 'number' && - typeof mockConfig.fullscreen === 'boolean' && - typeof mockConfig.debug === 'boolean' && - typeof mockConfig.name === 'string' && - mockConfig.name.length > 0; + return ( + typeof mockConfig.width === "number" && + typeof mockConfig.height === "number" && + typeof mockConfig.fullscreen === "boolean" && + typeof mockConfig.debug === "boolean" && + typeof mockConfig.name === "string" && + mockConfig.name.length > 0 + ); } catch (error) { return false; } }, - 'Should handle configuration merging properly' + "Should handle configuration merging properly", ); runner.addTest( - 'Build Command Generation', + "Build Command Generation", () => { try { // Test build command logic based on debug flag - const debugCommand = 'npm run build:debug'; - const releaseCommand = 'npm run build'; - + const debugCommand = "npm run build:debug"; + const releaseCommand = "npm run build"; + // Verify command generation logic const generateBuildCommand = (debug) => { return debug ? debugCommand : releaseCommand; }; - - return generateBuildCommand(true) === debugCommand && - generateBuildCommand(false) === releaseCommand; + + return ( + generateBuildCommand(true) === debugCommand && + generateBuildCommand(false) === releaseCommand + ); } catch (error) { return false; } }, - 'Should generate correct build commands for debug/release' + "Should generate correct build commands for debug/release", ); runner.addTest( - 'Remote Icon URL Validation', + "Remote Icon URL Validation", () => { try { // Test that remote icon URLs are properly validated const iconUrl = TEST_ASSETS.WEEKLY_ICNS; - + // Basic URL validation const url = new URL(iconUrl); - const isValidHttps = url.protocol === 'https:'; - const hasIconExtension = iconUrl.toLowerCase().endsWith('.icns'); - + const isValidHttps = url.protocol === "https:"; + const hasIconExtension = iconUrl.toLowerCase().endsWith(".icns"); + return isValidHttps && hasIconExtension; } catch (error) { return false; } }, - 'Should validate remote icon URLs correctly' + "Should validate remote icon URLs correctly", ); runner.addTest( - 'Icon Download Accessibility', + "Icon Download Accessibility", () => { try { // Test if the weekly.icns URL is accessible (without actually downloading) const iconUrl = TEST_ASSETS.WEEKLY_ICNS; - + // Quick URL format check - const expectedDomain = 'gw.alipayobjects.com'; - const expectedPath = '/os/k/fw/weekly.icns'; - + const expectedDomain = "gw.alipayobjects.com"; + const expectedPath = "/os/k/fw/weekly.icns"; + return iconUrl.includes(expectedDomain) && iconUrl.includes(expectedPath); } catch (error) { return false; } }, - 'Should have accessible CDN icon URL for testing' + "Should have accessible CDN icon URL for testing", ); // New Tauri runtime functionality tests based on src-tauri/src/ analysis runner.addTest( - 'Proxy URL Configuration', + "Proxy URL Configuration", () => { try { // Test proxy URL validation logic const validProxies = [ - 'http://127.0.0.1:7890', - 'https://proxy.example.com:8080', - 'socks5://127.0.0.1:7891' + "http://127.0.0.1:7890", + "https://proxy.example.com:8080", + "socks5://127.0.0.1:7891", ]; - - const invalidProxies = [ - 'not-a-url', - 'ftp://invalid-protocol.com', - '' - ]; - + + const invalidProxies = ["not-a-url", "ftp://invalid-protocol.com", ""]; + // Test valid proxy URLs - const validResults = validProxies.every(proxy => { + const validResults = validProxies.every((proxy) => { try { new URL(proxy); - return proxy.startsWith('http') || proxy.startsWith('socks5'); + return proxy.startsWith("http") || proxy.startsWith("socks5"); } catch { return false; } }); - + return validResults; } catch (error) { return false; } }, - 'Should validate proxy URL configurations' + "Should validate proxy URL configurations", ); runner.addTest( - 'User Agent String Validation', + "User Agent String Validation", () => { try { // Test user agent string handling const testUserAgents = [ - 'Pake/1.0 Weekly App', - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', - 'Custom-App/2.0', - '' // Empty should be allowed + "Pake/1.0 Weekly App", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", + "Custom-App/2.0", + "", // Empty should be allowed ]; - + // All should be valid strings - return testUserAgents.every(ua => typeof ua === 'string'); + return testUserAgents.every((ua) => typeof ua === "string"); } catch (error) { return false; } }, - 'Should handle user agent string configurations' + "Should handle user agent string configurations", ); runner.addTest( - 'Global Shortcut Key Format', + "Global Shortcut Key Format", () => { try { // Test global shortcut format validation const validShortcuts = [ - 'CmdOrControl+Shift+P', - 'Alt+F4', - 'Ctrl+Shift+X', - 'Cmd+Option+A', - '' // Empty should be allowed + "CmdOrControl+Shift+P", + "Alt+F4", + "Ctrl+Shift+X", + "Cmd+Option+A", + "", // Empty should be allowed ]; - - return validShortcuts.every(shortcut => { - if (shortcut === '') return true; + + return validShortcuts.every((shortcut) => { + if (shortcut === "") return true; // Check basic shortcut format: has modifiers and key separated by + - const parts = shortcut.split('+'); + const parts = shortcut.split("+"); if (parts.length < 2) return false; - + // Last part should be the key (letter or special key) const key = parts[parts.length - 1]; if (!key || key.length === 0) return false; - + // Check modifiers are valid const modifiers = parts.slice(0, -1); - const validModifiers = ['Cmd', 'Ctrl', 'CmdOrControl', 'Alt', 'Option', 'Shift', 'Meta']; - return modifiers.every(mod => validModifiers.includes(mod)); + const validModifiers = [ + "Cmd", + "Ctrl", + "CmdOrControl", + "Alt", + "Option", + "Shift", + "Meta", + ]; + return modifiers.every((mod) => validModifiers.includes(mod)); }); } catch (error) { return false; } }, - 'Should validate global shortcut key formats' + "Should validate global shortcut key formats", ); runner.addTest( - 'System Tray Configuration', + "System Tray Configuration", () => { try { // Test system tray option validation const trayConfigs = [ { show_system_tray: true, valid: true }, - { show_system_tray: false, valid: true } + { show_system_tray: false, valid: true }, ]; - - return trayConfigs.every(config => { - const isValidBoolean = typeof config.show_system_tray === 'boolean'; + + return trayConfigs.every((config) => { + const isValidBoolean = typeof config.show_system_tray === "boolean"; return isValidBoolean === config.valid; }); } catch (error) { return false; } }, - 'Should validate system tray configuration options' + "Should validate system tray configuration options", ); runner.addTest( - 'Window Behavior Configuration', + "Window Behavior Configuration", () => { try { // Test window behavior options const windowOptions = [ - 'hide_on_close', - 'fullscreen', - 'always_on_top', - 'resizable', - 'hide_title_bar', - 'dark_mode' + "hide_on_close", + "fullscreen", + "always_on_top", + "resizable", + "hide_title_bar", + "dark_mode", ]; - + // Test that all window options are recognized - return windowOptions.every(option => { - return typeof option === 'string' && option.length > 0; + return windowOptions.every((option) => { + return typeof option === "string" && option.length > 0; }); } catch (error) { return false; } }, - 'Should support all window behavior configurations' + "Should support all window behavior configurations", ); runner.addTest( - 'Download File Extension Detection', + "Download File Extension Detection", () => { try { // Test file download detection logic (from inject/event.js) const downloadableExtensions = [ - 'pdf', 'zip', 'dmg', 'msi', 'deb', 'AppImage', - 'jpg', 'png', 'gif', 'mp4', 'mp3', 'json' + "pdf", + "zip", + "dmg", + "msi", + "deb", + "AppImage", + "jpg", + "png", + "gif", + "mp4", + "mp3", + "json", ]; - + const testUrls = [ - 'https://example.com/file.pdf', - 'https://example.com/app.dmg', - 'https://example.com/archive.zip', - 'https://example.com/page.html' // Not downloadable + "https://example.com/file.pdf", + "https://example.com/app.dmg", + "https://example.com/archive.zip", + "https://example.com/page.html", // Not downloadable ]; - + // Test download detection logic const isDownloadUrl = (url) => { - const fileExtPattern = new RegExp(`\\.(${downloadableExtensions.join('|')})$`, 'i'); + const fileExtPattern = new RegExp( + `\\.(${downloadableExtensions.join("|")})$`, + "i", + ); return fileExtPattern.test(url); }; - - return testUrls.slice(0, 3).every(isDownloadUrl) && - !isDownloadUrl(testUrls[3]); + + return ( + testUrls.slice(0, 3).every(isDownloadUrl) && !isDownloadUrl(testUrls[3]) + ); } catch (error) { return false; } }, - 'Should detect downloadable file extensions correctly' + "Should detect downloadable file extensions correctly", ); runner.addTest( - 'Keyboard Shortcut Mapping', + "Keyboard Shortcut Mapping", () => { try { // Test keyboard shortcuts from inject/event.js const shortcuts = { - '[': 'history.back', - ']': 'history.forward', - '-': 'zoom.out', - '=': 'zoom.in', - '+': 'zoom.in', - '0': 'zoom.reset', - 'r': 'reload', - 'ArrowUp': 'scroll.top', - 'ArrowDown': 'scroll.bottom' + "[": "history.back", + "]": "history.forward", + "-": "zoom.out", + "=": "zoom.in", + "+": "zoom.in", + 0: "zoom.reset", + r: "reload", + ArrowUp: "scroll.top", + ArrowDown: "scroll.bottom", }; - + // Verify shortcut mapping structure - return Object.keys(shortcuts).length > 0 && - Object.values(shortcuts).every(action => typeof action === 'string'); + return ( + Object.keys(shortcuts).length > 0 && + Object.values(shortcuts).every((action) => typeof action === "string") + ); } catch (error) { return false; } }, - 'Should provide comprehensive keyboard shortcut mappings' + "Should provide comprehensive keyboard shortcut mappings", ); runner.addTest( - 'Locale-based Message Support', + "Locale-based Message Support", () => { try { // Test internationalization support from util.rs - const messageTypes = ['start', 'success', 'failure']; - const locales = ['en', 'zh']; - + const messageTypes = ["start", "success", "failure"]; + const locales = ["en", "zh"]; + // Test message structure - return messageTypes.every(type => typeof type === 'string') && - locales.every(locale => typeof locale === 'string'); + return ( + messageTypes.every((type) => typeof type === "string") && + locales.every((locale) => typeof locale === "string") + ); } catch (error) { return false; } }, - 'Should support locale-based download messages' + "Should support locale-based download messages", ); // Run the test suite @@ -879,4 +958,4 @@ if (import.meta.url === `file://${process.argv[1]}`) { runner.runAll().catch(console.error); } -export default runner; \ No newline at end of file +export default runner; diff --git a/tests/index.js b/tests/index.js index fd938ec..14ee778 100644 --- a/tests/index.js +++ b/tests/index.js @@ -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); }); diff --git a/tests/integration.test.js b/tests/integration.test.js index cf113a6..880664f 100644 --- a/tests/integration.test.js +++ b/tests/integration.test.js @@ -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; \ No newline at end of file +export default runner; diff --git a/tests/test.config.js b/tests/test.config.js index 8560120..e11f451 100644 --- a/tests/test.config.js +++ b/tests/test.config.js @@ -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 -}; \ No newline at end of file + testHelpers, +};