import { test, describe } from 'node:test'; import assert from 'node:assert'; import { existsSync, mkdirSync, writeFileSync, rmSync, 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); // 导入要测试的模块 import { smartInject } from '../../lib/framework-inject.js'; describe('框架注入集成测试', () => { const testDir = join(__dirname, 'fixtures'); test.before(() => { if (!existsSync(testDir)) { mkdirSync(testDir, { recursive: true }); } }); test.after(() => { // 清理所有测试文件 const filesToClean = [ 'vite.config.js', 'vite.config.ts', 'next.config.js', 'app/layout.tsx', 'app/layout.js', 'pages/_document.tsx', 'pages/_document.js', 'test.html' ]; filesToClean.forEach(file => { const filePath = join(testDir, file); if (existsSync(filePath)) { rmSync(filePath); } }); const dirsToClean = ['app', 'pages']; dirsToClean.forEach(dir => { const dirPath = join(testDir, dir); if (existsSync(dirPath)) { rmSync(dirPath, { recursive: true }); } }); }); describe('Vite 项目注入', () => { test('应该成功注入到 Vite 配置文件', () => { // 创建 Vite 配置文件 const viteConfig = `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { port: 3000 } })`; writeFileSync(join(testDir, 'vite.config.js'), viteConfig); // 执行注入 const result = smartInject({ remote: 'http://localhost:9000/dev-monitor.js', dryRun: true, verbose: true }, testDir); assert.strictEqual(result, true); }); test('应该检查已存在的插件', () => { // 创建已有插件的 Vite 配置 const viteConfigWithPlugin = `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [ react(), // dev-inject-plugin { name: 'dev-inject-plugin', transformIndexHtml(html) { if (process.env.NODE_ENV === 'development') { return html.replace('', ''); } return html; } } ], server: { port: 3000 } })`; writeFileSync(join(testDir, 'vite.config.js'), viteConfigWithPlugin); // 执行注入(应该检测到已存在) const result = smartInject({ remote: 'http://localhost:9000/dev-monitor.js', dryRun: true }, testDir); assert.strictEqual(result, true); }); test('应该处理没有 plugins 数组的情况', () => { // 创建没有 plugins 的配置 const simpleConfig = `export default { server: { port: 3000 } }`; writeFileSync(join(testDir, 'vite.config.js'), simpleConfig); const result = smartInject({ remote: '/scripts/monitor.js', dryRun: true }, testDir); assert.strictEqual(result, true); }); test('应该处理 TypeScript 配置文件', () => { const tsConfig = `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()] })`; writeFileSync(join(testDir, 'vite.config.ts'), tsConfig); const result = smartInject({ remote: '/scripts/monitor.js', dryRun: true }, testDir); assert.strictEqual(result, true); }); }); describe('Next.js App Router 注入', () => { test('应该成功注入到 app/layout.tsx', () => { // 创建 Next.js App Router 结构 mkdirSync(join(testDir, 'app'), { recursive: true }); writeFileSync(join(testDir, 'next.config.js'), 'module.exports = {}'); const layoutContent = `import type { Metadata } from 'next' import { Inter } from 'next/font/google' import './globals.css' const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { title: 'Next.js App', description: 'Generated by create next app', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ) }`; writeFileSync(join(testDir, 'app', 'layout.tsx'), layoutContent); const result = smartInject({ remote: 'http://localhost:9000/dev-monitor.js', dryRun: true }, testDir); assert.strictEqual(result, true); }); test('应该处理 app/layout.js', () => { mkdirSync(join(testDir, 'app'), { recursive: true }); writeFileSync(join(testDir, 'next.config.js'), 'module.exports = {}'); const jsLayout = `import './globals.css' export default function RootLayout({ children }) { return ( {children} ) }`; writeFileSync(join(testDir, 'app', 'layout.js'), jsLayout); const result = smartInject({ remote: '/scripts/monitor.js', dryRun: true }, testDir); assert.strictEqual(result, true); }); }); describe('Next.js Pages Router 注入', () => { test('应该成功注入到 pages/_document.tsx', () => { mkdirSync(join(testDir, 'pages'), { recursive: true }); writeFileSync(join(testDir, 'next.config.js'), 'module.exports = {}'); const documentContent = `import { Html, Head, Main, NextScript } from 'next/document' export default function Document() { return (
) }`; writeFileSync(join(testDir, 'pages', '_document.tsx'), documentContent); const result = smartInject({ remote: 'http://localhost:9000/dev-monitor.js', dryRun: true }, testDir); assert.strictEqual(result, true); }); test('应该处理 pages/_document.js', () => { mkdirSync(join(testDir, 'pages'), { recursive: true }); writeFileSync(join(testDir, 'next.config.js'), 'module.exports = {}'); const jsDocument = `const { Html, Head, Main, NextScript } = require('next/document') export default function Document() { return ( My App
) }`; writeFileSync(join(testDir, 'pages', '_document.js'), jsDocument); const result = smartInject({ remote: '/scripts/monitor.js', dryRun: true }, testDir); assert.strictEqual(result, true); }); }); describe('混合模式处理', () => { test('应该优先注入到 app/layout', () => { // 创建混合模式结构 mkdirSync(join(testDir, 'app'), { recursive: true }); mkdirSync(join(testDir, 'pages'), { recursive: true }); writeFileSync(join(testDir, 'next.config.js'), 'module.exports = {}'); writeFileSync(join(testDir, 'app', 'layout.tsx'), 'export default function Layout() { return
App Layout
; }'); writeFileSync(join(testDir, 'pages', '_document.tsx'), 'export default function Document() { return
Document
; }'); const result = smartInject({ remote: '/scripts/monitor.js', dryRun: true }, testDir); assert.strictEqual(result, true); }); }); describe('错误处理', () => { test('应该处理不支持的框架类型', () => { writeFileSync(join(testDir, 'index.html'), ''); try { smartInject({ remote: '/scripts/monitor.js', dryRun: false }, testDir); assert.fail('应该抛出错误'); } catch (error) { assert.match(error.message, /不支持的项目类型/); } }); test('应该处理配置文件不存在的情况', () => { writeFileSync(join(testDir, 'next.config.js'), 'module.exports = {}'); // 不创建 layout 文件 try { smartInject({ remote: '/scripts/monitor.js', dryRun: false }, testDir); assert.fail('应该抛出错误'); } catch (error) { assert.match(error.message, /未找到/); } }); test('应该处理无效的配置文件', () => { writeFileSync(join(testDir, 'vite.config.js'), 'invalid javascript content'); try { smartInject({ remote: '/scripts/monitor.js', dryRun: false }, testDir); assert.fail('应该抛出错误'); } catch (error) { assert.ok(error.message.length > 0); // 应该有某种错误信息 } }); }); describe('实际文件修改测试', () => { test('应该实际修改 Vite 配置文件', () => { const originalConfig = `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { port: 3000 } })`; writeFileSync(join(testDir, 'vite.config.js'), originalConfig); // 执行实际注入 const result = smartInject({ remote: '/scripts/monitor.js', dryRun: false, verbose: false }, testDir); assert.strictEqual(result, true); // 验证文件被修改 const modifiedContent = readFileSync(join(testDir, 'vite.config.js'), 'utf8'); assert.match(modifiedContent, /dev-inject-plugin/); assert.match(modifiedContent, /scripts\/monitor\.js/); // 验证原始内容保留 assert.match(modifiedContent, /defineConfig/); assert.match(modifiedContent, /react\(\)/); }); test('应该能够移除注入的内容', () => { // 先注入 smartInject({ remote: '/scripts/monitor.js', dryRun: false }, testDir); // 然后移除 const result = removeInjection({ dryRun: false, verbose: false }, testDir); assert.strictEqual(result, true); // 验证插件被移除 const cleanedContent = readFileSync(join(testDir, 'vite.config.js'), 'utf8'); assert(!cleanedContent.includes('dev-inject-plugin')); }); }); describe('不同脚本类型', () => { test('应该处理 HTTP URL', () => { writeFileSync(join(testDir, 'vite.config.js'), 'export default { plugins: [] }'); const result = smartInject({ remote: 'https://cdn.example.com/monitor.js', dryRun: true }, testDir); assert.strictEqual(result, true); }); test('应该处理本地路径', () => { writeFileSync(join(testDir, 'vite.config.js'), 'export default { plugins: [] }'); const result = smartInject({ remote: '/assets/dev-monitor.js', dryRun: true }, testDir); assert.strictEqual(result, true); }); }); });