962 lines
26 KiB
JavaScript
962 lines
26 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* 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";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const projectRoot = config.PROJECT_ROOT;
|
|
const cliPath = config.CLI_PATH;
|
|
|
|
// Test utilities
|
|
class TestRunner {
|
|
constructor() {
|
|
this.tests = [];
|
|
this.results = [];
|
|
}
|
|
|
|
addTest(name, testFn, description = "") {
|
|
this.tests.push({ name, testFn, description });
|
|
}
|
|
|
|
async runAll() {
|
|
console.log("🧪 Pake CLI Test Suite");
|
|
console.log("======================\n");
|
|
|
|
// Quick validation
|
|
this.validateEnvironment();
|
|
|
|
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) {
|
|
spinner.succeed(`${index + 1}. ${test.name}: PASS`);
|
|
this.results.push({ name: test.name, passed: true });
|
|
} else {
|
|
spinner.fail(`${index + 1}. ${test.name}: FAIL`);
|
|
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,
|
|
});
|
|
}
|
|
}
|
|
|
|
this.displayResults();
|
|
this.displayManualTestScenarios();
|
|
}
|
|
|
|
validateEnvironment() {
|
|
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");
|
|
process.exit(1);
|
|
}
|
|
console.log("✅ CLI file exists");
|
|
|
|
// Check if CLI is executable
|
|
try {
|
|
execSync(`node "${cliPath}" --version`, {
|
|
encoding: "utf8",
|
|
timeout: 3000,
|
|
});
|
|
console.log("✅ CLI is executable");
|
|
} catch (error) {
|
|
console.log("❌ CLI is not executable");
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log("✅ Environment is ready\n");
|
|
}
|
|
|
|
displayResults() {
|
|
const passed = this.results.filter((r) => r.passed).length;
|
|
const total = this.results.length;
|
|
|
|
console.log("\n📊 Test Results:");
|
|
console.log("================");
|
|
|
|
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");
|
|
} else {
|
|
console.log(`❌ ${total - passed} test(s) failed\n`);
|
|
}
|
|
}
|
|
|
|
displayManualTestScenarios() {
|
|
console.log("🔧 Manual Test Scenarios:");
|
|
console.log("=========================\n");
|
|
|
|
const scenarios = [
|
|
{
|
|
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)",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
name: "Full Screen App",
|
|
command: `node "${cliPath}" ${TEST_URLS.WEEKLY} --name "FullWeekly" --fullscreen`,
|
|
description: "Creates full-screen Weekly app",
|
|
expectedTime: "2-5 minutes",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
];
|
|
|
|
if (process.platform === "darwin") {
|
|
scenarios.push(
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
);
|
|
}
|
|
|
|
if (process.platform === "linux") {
|
|
scenarios.push(
|
|
{
|
|
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",
|
|
},
|
|
{
|
|
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",
|
|
},
|
|
);
|
|
}
|
|
|
|
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) => {
|
|
console.log(`${index + 1}. ${scenario.name}`);
|
|
console.log(` Command: ${scenario.command}`);
|
|
console.log(` Description: ${scenario.description}`);
|
|
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");
|
|
}
|
|
}
|
|
|
|
// Test suite implementation
|
|
const runner = new TestRunner();
|
|
|
|
// Unit Tests
|
|
runner.addTest(
|
|
"Version Command",
|
|
() => {
|
|
const output = execSync(`node "${cliPath}" --version`, {
|
|
encoding: "utf8",
|
|
timeout: 3000,
|
|
});
|
|
return /^\d+\.\d+\.\d+/.test(output.trim());
|
|
},
|
|
"Should output version number",
|
|
);
|
|
|
|
runner.addTest(
|
|
"Help Command",
|
|
() => {
|
|
const output = execSync(`node "${cliPath}" --help`, {
|
|
encoding: "utf8",
|
|
timeout: 3000,
|
|
});
|
|
return output.includes("Usage: cli [url] [options]");
|
|
},
|
|
"Should display help information",
|
|
);
|
|
|
|
runner.addTest(
|
|
"No Arguments Behavior",
|
|
() => {
|
|
const output = execSync(`node "${cliPath}"`, {
|
|
encoding: "utf8",
|
|
timeout: 3000,
|
|
});
|
|
return output.includes("Usage: cli [url] [options]");
|
|
},
|
|
"Should display help when no arguments provided",
|
|
);
|
|
|
|
runner.addTest(
|
|
"Invalid Number Validation",
|
|
() => {
|
|
try {
|
|
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");
|
|
}
|
|
},
|
|
"Should reject invalid number inputs",
|
|
);
|
|
|
|
runner.addTest(
|
|
"URL Validation",
|
|
() => {
|
|
try {
|
|
// Test with a clearly invalid URL that should fail
|
|
execSync(`node "${cliPath}" "${TEST_URLS.INVALID}" --name TestApp`, {
|
|
encoding: "utf8",
|
|
timeout: 3000,
|
|
});
|
|
return false; // Should have failed
|
|
} catch (error) {
|
|
// Should fail with non-zero exit code for invalid URL
|
|
return error.status !== 0;
|
|
}
|
|
},
|
|
"Should reject malformed URLs",
|
|
);
|
|
|
|
runner.addTest(
|
|
"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 hasEssentialDeps = [
|
|
"commander",
|
|
"chalk",
|
|
"fs-extra",
|
|
"execa",
|
|
].every((dep) => packageJson.dependencies[dep]);
|
|
|
|
return hasEssentialDeps;
|
|
} catch {
|
|
return false;
|
|
}
|
|
},
|
|
"Should have all required dependencies",
|
|
);
|
|
|
|
// Performance and Integration Tests
|
|
runner.addTest(
|
|
"CLI Response Time",
|
|
() => {
|
|
const start = Date.now();
|
|
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",
|
|
);
|
|
|
|
runner.addTest(
|
|
"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");
|
|
},
|
|
"Should support debug build options",
|
|
);
|
|
|
|
// New comprehensive option validation tests
|
|
runner.addTest(
|
|
"CLI Options Validation - Core Options Present",
|
|
() => {
|
|
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",
|
|
];
|
|
|
|
return coreOptions.every((option) => output.includes(option));
|
|
},
|
|
"Should include core CLI options",
|
|
);
|
|
|
|
runner.addTest(
|
|
"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,
|
|
});
|
|
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")
|
|
);
|
|
}
|
|
},
|
|
"Should accept weekly.tw93.fun as valid URL",
|
|
);
|
|
|
|
runner.addTest(
|
|
"App Version Format Validation",
|
|
() => {
|
|
try {
|
|
// Test with valid version format first
|
|
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");
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should have app version option available",
|
|
);
|
|
|
|
runner.addTest(
|
|
"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,
|
|
},
|
|
);
|
|
// Should not immediately fail on valid shortcut format
|
|
return !output.includes("Invalid shortcut");
|
|
} catch (error) {
|
|
return !error.message.includes("Invalid shortcut");
|
|
}
|
|
},
|
|
"Should accept valid activation shortcut format",
|
|
);
|
|
|
|
// Critical implementation tests based on bin/ analysis
|
|
|
|
runner.addTest(
|
|
"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 },
|
|
];
|
|
|
|
// Test name validation logic exists
|
|
return testCases.every((testCase) => {
|
|
if (testCase.name === "") {
|
|
// Empty names should be handled
|
|
return true;
|
|
}
|
|
// Non-empty names should be accepted
|
|
return testCase.name.length > 0;
|
|
});
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should handle various app name formats for file naming",
|
|
);
|
|
|
|
runner.addTest(
|
|
"Platform-specific Build Output Validation",
|
|
() => {
|
|
try {
|
|
// Verify that platform detection works properly
|
|
const platform = process.platform;
|
|
const expectedExtensions = {
|
|
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",
|
|
);
|
|
|
|
runner.addTest(
|
|
"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",
|
|
];
|
|
|
|
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) => {
|
|
try {
|
|
new URL(url);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
});
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should properly validate and process URLs",
|
|
);
|
|
|
|
runner.addTest(
|
|
"Icon Format Validation",
|
|
() => {
|
|
try {
|
|
// Test icon extension validation logic based on platform
|
|
const platform = process.platform;
|
|
const validIconExtensions = {
|
|
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",
|
|
);
|
|
|
|
runner.addTest(
|
|
"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 isValidInjectionFile = (filename) => {
|
|
return (
|
|
filename.toLowerCase().endsWith(".css") ||
|
|
filename.toLowerCase().endsWith(".js")
|
|
);
|
|
};
|
|
|
|
const validResults = validFiles.every(isValidInjectionFile);
|
|
const invalidResults = invalidFiles.every(
|
|
(file) => !isValidInjectionFile(file),
|
|
);
|
|
|
|
return validResults && invalidResults;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should validate injection file formats (CSS/JS only)",
|
|
);
|
|
|
|
runner.addTest(
|
|
"Configuration Merging Logic",
|
|
() => {
|
|
try {
|
|
// Test that configuration options are properly structured
|
|
const mockConfig = {
|
|
width: 1200,
|
|
height: 800,
|
|
fullscreen: false,
|
|
debug: false,
|
|
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
|
|
);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should handle configuration merging properly",
|
|
);
|
|
|
|
runner.addTest(
|
|
"Build Command Generation",
|
|
() => {
|
|
try {
|
|
// Test build command logic based on debug flag
|
|
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
|
|
);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should generate correct build commands for debug/release",
|
|
);
|
|
|
|
runner.addTest(
|
|
"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");
|
|
|
|
return isValidHttps && hasIconExtension;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should validate remote icon URLs correctly",
|
|
);
|
|
|
|
runner.addTest(
|
|
"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";
|
|
|
|
return iconUrl.includes(expectedDomain) && iconUrl.includes(expectedPath);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should have accessible CDN icon URL for testing",
|
|
);
|
|
|
|
// New Tauri runtime functionality tests based on src-tauri/src/ analysis
|
|
|
|
runner.addTest(
|
|
"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",
|
|
];
|
|
|
|
const invalidProxies = ["not-a-url", "ftp://invalid-protocol.com", ""];
|
|
|
|
// Test valid proxy URLs
|
|
const validResults = validProxies.every((proxy) => {
|
|
try {
|
|
new URL(proxy);
|
|
return proxy.startsWith("http") || proxy.startsWith("socks5");
|
|
} catch {
|
|
return false;
|
|
}
|
|
});
|
|
|
|
return validResults;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should validate proxy URL configurations",
|
|
);
|
|
|
|
runner.addTest(
|
|
"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
|
|
];
|
|
|
|
// All should be valid strings
|
|
return testUserAgents.every((ua) => typeof ua === "string");
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should handle user agent string configurations",
|
|
);
|
|
|
|
runner.addTest(
|
|
"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
|
|
];
|
|
|
|
return validShortcuts.every((shortcut) => {
|
|
if (shortcut === "") return true;
|
|
// Check basic shortcut format: has modifiers and key separated by +
|
|
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));
|
|
});
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should validate global shortcut key formats",
|
|
);
|
|
|
|
runner.addTest(
|
|
"System Tray Configuration",
|
|
() => {
|
|
try {
|
|
// Test system tray option validation
|
|
const trayConfigs = [
|
|
{ show_system_tray: true, valid: true },
|
|
{ show_system_tray: false, valid: true },
|
|
];
|
|
|
|
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",
|
|
);
|
|
|
|
runner.addTest(
|
|
"Window Behavior Configuration",
|
|
() => {
|
|
try {
|
|
// Test window behavior options
|
|
const windowOptions = [
|
|
"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;
|
|
});
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should support all window behavior configurations",
|
|
);
|
|
|
|
runner.addTest(
|
|
"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",
|
|
];
|
|
|
|
const testUrls = [
|
|
"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",
|
|
);
|
|
return fileExtPattern.test(url);
|
|
};
|
|
|
|
return (
|
|
testUrls.slice(0, 3).every(isDownloadUrl) && !isDownloadUrl(testUrls[3])
|
|
);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should detect downloadable file extensions correctly",
|
|
);
|
|
|
|
runner.addTest(
|
|
"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",
|
|
};
|
|
|
|
// Verify shortcut mapping structure
|
|
return (
|
|
Object.keys(shortcuts).length > 0 &&
|
|
Object.values(shortcuts).every((action) => typeof action === "string")
|
|
);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should provide comprehensive keyboard shortcut mappings",
|
|
);
|
|
|
|
runner.addTest(
|
|
"Locale-based Message Support",
|
|
() => {
|
|
try {
|
|
// Test internationalization support from util.rs
|
|
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")
|
|
);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
"Should support locale-based download messages",
|
|
);
|
|
|
|
// Run the test suite
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
runner.runAll().catch(console.error);
|
|
}
|
|
|
|
export default runner;
|