import { test, describe } from 'node:test'; import assert from 'node:assert'; import { execSync, spawn } from 'child_process'; import { readFileSync } from 'fs'; import { join } from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); describe('端到端测试', () => { const testDir = join(__dirname, 'fixtures'); const cliPath = join(__dirname, '..', '..', 'bin', 'index.js'); const projectRoot = join(__dirname, '..', '..'); // 创建测试项目的基本结构 const testProjects = { vite: { name: 'test-vite-project', files: { 'package.json': JSON.stringify({ "name": "test-vite-project", "version": "1.0.0", "type": "module", "scripts": { "dev": "vite" }, "dependencies": { "react": "^18.2.0" } }, null, 2), 'vite.config.js': `import { defineConfig } from 'vite' export default defineConfig({ plugins: [] })`, 'index.html': ` Vite Test
`, 'src/main.js': `import './app.js'`, 'src/app.js': `console.log('Vite app loaded'); document.getElementById('app').innerHTML = '

Vite App

';` } }, 'next-app': { name: 'test-next-app', files: { 'package.json': JSON.stringify({ "name": "test-next-app", "version": "1.0.0", "scripts": { "dev": "next dev" } }, null, 2), 'next.config.js': 'module.exports = {}', 'app/layout.tsx': `import './globals.css' export default function RootLayout({ children }) { return ( {children} )}`, 'app/page.tsx': `export default function Home() { return

Next.js App

; }`, 'app/globals.css': 'body { font-family: Arial, sans-serif; }' } }, 'html-static': { name: 'test-html-project', files: { 'index.html': ` Static HTML

Static HTML Project

This is a static HTML project.

` } } }; function createProject(projectName, files) { const projectPath = join(testDir, projectName); mkdirSync(projectPath, { recursive: true }); for (const [filePath, content] of Object.entries(files)) { const fullPath = join(projectPath, filePath); const dir = dirname(fullPath); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } writeFileSync(fullPath, content); } return projectPath; } function cleanupProject(projectPath) { if (existsSync(projectPath)) { rmSync(projectPath, { recursive: true }); } } function runCliInProject(projectPath, args, options = {}) { try { const result = execSync(`node ${cliPath} ${args}`, { encoding: 'utf8', cwd: projectPath, stdio: ['ignore', 'pipe', 'pipe'], timeout: options.timeout || 10000 }); return { success: true, output: result }; } catch (error) { return { success: false, output: error.stdout || error.message, code: error.status, signal: error.signal }; } } test.before(() => { if (!existsSync(testDir)) { mkdirSync(testDir, { recursive: true }); } }); test.after(() => { // 清理所有测试项目 Object.keys(testProjects).forEach(projectName => { const projectPath = join(testDir, testProjects[projectName].name); cleanupProject(projectPath); }); }); describe('Vite 项目完整流程', () => { test('应该完成 Vite 项目的完整注入流程', () => { const projectPath = createProject(testProjects.vite.name, testProjects.vite.files); try { // 1. 检测项目类型 const detectResult = runCliInProject(projectPath, 'install --remote=/scripts/monitor.js --framework --dry-run --verbose'); assert.strictEqual(detectResult.success, true); assert.match(detectResult.output, /检测到项目类型: vite/); // 2. 执行实际注入 const injectResult = runCliInProject(projectPath, 'install --remote=/scripts/monitor.js --framework'); assert.strictEqual(injectResult.success, true); assert.match(injectResult.output, /框架注入成功/); // 3. 验证配置文件被修改 const viteConfigPath = join(projectPath, 'vite.config.js'); assert(existsSync(viteConfigPath)); const configContent = readFileSync(viteConfigPath, 'utf8'); assert.match(configContent, /dev-inject-plugin/); assert.match(configContent, /scripts\/monitor\.js/); // 4. 验证原始配置保留 assert.match(configContent, /defineConfig/); // 5. 执行卸载 const uninstallResult = runCliInProject(projectPath, 'uninstall --framework'); assert.strictEqual(uninstallResult.success, true); // 6. 验证插件被移除 const cleanedContent = readFileSync(viteConfigPath, 'utf8'); assert(!cleanedContent.includes('dev-inject-plugin')); } finally { cleanupProject(projectPath); } }); test('应该处理不同的脚本类型', () => { const projectPath = createProject(testProjects.vite.name, testProjects.vite.files); try { // 测试 HTTP URL const httpResult = runCliInProject(projectPath, 'install --remote=https://cdn.example.com/monitor.js --framework --dry-run'); assert.strictEqual(httpResult.success, true); assert.match(httpResult.output, /检测到项目类型: vite/); // 测试本地路径 const localResult = runCliInProject(projectPath, 'install --remote=/assets/dev-monitor.js --framework --dry-run'); assert.strictEqual(localResult.success, true); assert.match(localResult.output, /检测到项目类型: vite/); } finally { cleanupProject(projectPath); } }); }); describe('Next.js 项目完整流程', () => { test('应该完成 Next.js App Router 的完整注入流程', () => { const projectPath = createProject(testProjects['next-app'].name, testProjects['next-app'].files); try { // 1. 检测项目类型 const detectResult = runCliInProject(projectPath, 'install --remote=/scripts/monitor.js --framework --dry-run --verbose'); assert.strictEqual(detectResult.success, true); assert.match(detectResult.output, /检测到项目类型: next-app/); // 2. 执行实际注入 const injectResult = runCliInProject(projectPath, 'install --remote=/scripts/monitor.js --framework'); assert.strictEqual(injectResult.success, true); assert.match(injectResult.output, /框架注入成功/); // 3. 验证 layout 文件被修改 const layoutPath = join(projectPath, 'app/layout.tsx'); assert(existsSync(layoutPath)); const layoutContent = readFileSync(layoutPath, 'utf8'); assert.match(layoutContent, /process\.env\.NODE_ENV === 'development'/); assert.match(layoutContent, /scripts\/monitor\.js/); // 4. 验证原始内容保留 assert.match(layoutContent, /RootLayout/); assert.match(layoutContent, /children/); // 5. 执行卸载 const uninstallResult = runCliInProject(projectPath, 'uninstall --framework'); assert.strictEqual(uninstallResult.success, true); // 6. 验证注入内容被移除 const cleanedContent = readFileSync(layoutPath, 'utf8'); assert(!cleanedContent.includes('process.env.NODE_ENV')); assert(!cleanedContent.includes('scripts/monitor.js')); } finally { cleanupProject(projectPath); } }); }); describe('静态 HTML 项目完整流程', () => { test('应该完成静态 HTML 项目的完整注入流程', () => { const projectPath = createProject(testProjects['html-static'].name, testProjects['html-static'].files); try { // 1. 检测项目类型 const detectResult = runCliInProject(projectPath, 'install --remote=/scripts/monitor.js --framework --dry-run --verbose'); assert.strictEqual(detectResult.success, true); assert.match(detectResult.output, /检测到项目类型: html-static/); // 2. 执行实际注入 const injectResult = runCliInProject(projectPath, 'install --remote=/scripts/monitor.js --framework'); assert.strictEqual(injectResult.success, true); assert.match(injectResult.output, /成功处理 \d+ 个文件/); // 3. 验证 HTML 文件被修改 const htmlPath = join(projectPath, 'index.html'); assert(existsSync(htmlPath)); const htmlContent = readFileSync(htmlPath, 'utf8'); assert.match(htmlContent, /