Files
qiming/qiming-dev-inject/test/integration/smartInject.test.js
2026-06-01 12:53:19 +08:00

410 lines
11 KiB
JavaScript

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('</head>', '<script src="/existing.js"></script></head>');
}
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 (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
)
}`;
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 (
<html lang="en">
<body>{children}</body>
</html>
)
}`;
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 (
<Html lang="en">
<Head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}`;
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 (
<Html lang="en">
<Head>
<title>My App</title>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}`;
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 <div>App Layout</div>; }');
writeFileSync(join(testDir, 'pages', '_document.tsx'), 'export default function Document() { return <div>Document</div>; }');
const result = smartInject({
remote: '/scripts/monitor.js',
dryRun: true
}, testDir);
assert.strictEqual(result, true);
});
});
describe('错误处理', () => {
test('应该处理不支持的框架类型', () => {
writeFileSync(join(testDir, 'index.html'), '<html></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);
});
});
});