Skip to main content
Glama
modernization.test.ts8.56 kB
/** * 現代化分析器單元測試 */ import { describe, it, expect, beforeEach } from 'vitest'; import { ModernizationAnalyzer } from '../../analyzers/modernization.js'; import { CodeParser } from '../../parsers/index.js'; describe('ModernizationAnalyzer', () => { let analyzer: ModernizationAnalyzer; let parser: CodeParser; beforeEach(() => { parser = new CodeParser(); analyzer = new ModernizationAnalyzer(parser); }); describe('var 宣告檢測', () => { it('應該檢測 var 宣告並建議使用 let/const', () => { const code = `var x = 1; var y = 2;`; const parsed = parser.parseCode(code, 'test.js'); // 使用 private 方法需要用 type assertion const result = (analyzer as any).analyzeFile(parsed); expect(result.suggestions.some((s: any) => s.id.includes('var-to') && s.type === 'syntax-modernization' )).toBe(true); }); it('應該為函式賦值建議使用 const', () => { const code = `var myFunc = function() { return 1; };`; const parsed = parser.parseCode(code, 'test.js'); const result = (analyzer as any).analyzeFile(parsed); expect(result.suggestions.some((s: any) => s.id === 'var-to-const' )).toBe(true); }); }); describe('函式庫檢測', () => { it('應該檢測 jQuery import', () => { const code = `import $ from 'jquery';`; const parsed = parser.parseCode(code, 'test.js'); const result = (analyzer as any).analyzeFile(parsed); expect(result.suggestions.some((s: any) => s.id.includes('jquery') && s.type === 'library-replacement' )).toBe(true); }); it('應該檢測 Moment.js import', () => { const code = `import moment from 'moment';`; const parsed = parser.parseCode(code, 'test.js'); const result = (analyzer as any).analyzeFile(parsed); expect(result.suggestions.some((s: any) => s.id.includes('moment') && s.type === 'library-replacement' )).toBe(true); }); it('應該檢測 Lodash import', () => { const code = `import _ from 'lodash';`; const parsed = parser.parseCode(code, 'test.js'); const result = (analyzer as any).analyzeFile(parsed); expect(result.suggestions.some((s: any) => s.id.includes('lodash') && s.type === 'library-replacement' )).toBe(true); }); }); describe('IIFE 檢測', () => { it('應該檢測 IIFE 模式', () => { const code = `(function() { var x = 1; })();`; const parsed = parser.parseCode(code, 'test.js'); const result = (analyzer as any).analyzeFile(parsed); expect(result.suggestions.some((s: any) => s.id.includes('iife-to-module') )).toBe(true); }); it('應該檢測箭頭函式 IIFE', () => { const code = `(() => { const x = 1; })();`; const parsed = parser.parseCode(code, 'test.js'); const result = (analyzer as any).analyzeFile(parsed); expect(result.suggestions.some((s: any) => s.id.includes('iife-to-module') )).toBe(true); }); }); describe('for 迴圈檢測', () => { it('應該檢測可轉換為 forEach 的 for 迴圈', () => { const code = ` const arr = [1, 2, 3]; for (let i = 0; i < arr.length; i++) { console.log(arr[i]); } `; const parsed = parser.parseCode(code, 'test.js'); const result = (analyzer as any).analyzeFile(parsed); expect(result.suggestions.some((s: any) => s.id.includes('for-to-') && s.type === 'pattern-modernization' )).toBe(true); }); it('應該檢測 for...in 迴圈', () => { const code = ` const obj = { a: 1 }; for (const key in obj) { console.log(key); } `; const parsed = parser.parseCode(code, 'test.js'); const result = (analyzer as any).analyzeFile(parsed); expect(result.suggestions.some((s: any) => s.id.includes('for-in-to-for-of') )).toBe(true); }); }); describe('風險評估', () => { it('低風險變更應該回傳低風險等級', () => { const suggestions = [ { breaking: false, difficulty: 'trivial', type: 'syntax-modernization' } ]; const risk = (analyzer as any).assessRisk(suggestions); expect(risk.overallRisk).toBe('low'); }); it('多個破壞性變更應該回傳高風險等級', () => { const suggestions = Array(10).fill({ breaking: true, difficulty: 'complex', type: 'library-replacement' }); const risk = (analyzer as any).assessRisk(suggestions); expect(risk.overallRisk).toBe('high'); }); it('應該正確計算預估工時', () => { const suggestions = [ { breaking: true, difficulty: 'complex' }, // 8 hours { breaking: true, difficulty: 'moderate' }, // 2 hours { breaking: false, difficulty: 'simple' }, // 0.5 hours { breaking: false, difficulty: 'trivial' } // 0.5 hours ]; const risk = (analyzer as any).assessRisk(suggestions); expect(risk.migrationEffort.estimatedHours).toBe(11); // 8 + 2 + 0.5 + 0.5 }); }); }); describe('CodeParser', () => { let parser: CodeParser; beforeEach(() => { parser = new CodeParser(); }); describe('ES Module 解析', () => { it('應該解析 default import', () => { const code = `import React from 'react';`; const result = parser.parseCode(code, 'test.js'); expect(result.imports).toHaveLength(1); expect(result.imports[0].source).toBe('react'); expect(result.imports[0].specifiers[0].type).toBe('default'); }); it('應該解析 named imports', () => { const code = `import { useState, useEffect } from 'react';`; const result = parser.parseCode(code, 'test.js'); expect(result.imports).toHaveLength(1); expect(result.imports[0].specifiers).toHaveLength(2); expect(result.imports[0].specifiers[0].imported).toBe('useState'); }); it('應該解析 namespace import', () => { const code = `import * as React from 'react';`; const result = parser.parseCode(code, 'test.js'); expect(result.imports[0].specifiers[0].type).toBe('namespace'); }); }); describe('CommonJS 解析', () => { it('應該解析 require()', () => { const code = `const fs = require('fs');`; const result = parser.parseCode(code, 'test.js'); expect(result.imports.some(i => i.source === 'fs')).toBe(true); }); it('應該解析解構 require()', () => { const code = `const { readFile, writeFile } = require('fs');`; const result = parser.parseCode(code, 'test.js'); const fsImport = result.imports.find(i => i.source === 'fs'); expect(fsImport).toBeDefined(); expect(fsImport?.specifiers.some(s => s.imported === 'readFile')).toBe(true); }); }); describe('函式解析', () => { it('應該解析函式宣告', () => { const code = `function myFunc(a, b) { return a + b; }`; const result = parser.parseCode(code, 'test.js'); expect(result.functions.some(f => f.name === 'myFunc')).toBe(true); }); it('應該解析 async 函式', () => { const code = `async function fetchData() { return await fetch('/api'); }`; const result = parser.parseCode(code, 'test.js'); const asyncFunc = result.functions.find(f => f.name === 'fetchData'); expect(asyncFunc?.isAsync).toBe(true); }); it('應該解析箭頭函式', () => { const code = `const add = (a, b) => a + b;`; const result = parser.parseCode(code, 'test.js'); expect(result.functions.some(f => f.isArrow)).toBe(true); }); }); describe('API 呼叫解析', () => { it('應該解析方法呼叫', () => { const code = `document.getElementById('app');`; const result = parser.parseCode(code, 'test.js'); const call = result.apiCalls.find(c => c.api === 'document' && c.method === 'getElementById' ); expect(call).toBeDefined(); }); it('應該解析函式呼叫', () => { const code = `console.log('hello');`; const result = parser.parseCode(code, 'test.js'); const call = result.apiCalls.find(c => c.api === 'console' && c.method === 'log' ); expect(call).toBeDefined(); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mukiwu/dev-advisor-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server