Skip to main content
Glama

Motion.dev MCP Server

generation.test.ts23.1 kB
/** * Test suite for code generation MCP tools */ import { CodeGenerationTool } from '../../src/tools/generation.js'; import { Framework } from '../../src/types/motion.js'; describe('CodeGenerationTool', () => { let tool: CodeGenerationTool; beforeEach(() => { tool = new CodeGenerationTool(); }); describe('generateMotionComponent', () => { test('should generate React component from pattern', async () => { const params = { pattern: 'fade-in', framework: 'react' as Framework, componentName: 'FadeButton', typescript: true }; const response = await tool.generateMotionComponent(params); expect(response.success).toBe(true); expect(response.code).toContain('import React from \'react\''); expect(response.code).toContain('import { motion } from \'framer-motion\''); expect(response.code).toContain('const FadeButton'); expect(response.code).toContain('motion.div'); expect(response.framework).toBe('react'); expect(response.typescript).toBe(true); expect(response.componentName).toBe('FadeButton'); }); test('should generate Vue component from pattern', async () => { const params = { pattern: 'slide-up', framework: 'vue' as Framework, componentName: 'SlideCard', typescript: false }; const response = await tool.generateMotionComponent(params); expect(response.success).toBe(true); expect(response.code).toContain('<template>'); expect(response.code).toContain('<script>'); expect(response.code).toContain('v-motion'); expect(response.framework).toBe('vue'); expect(response.typescript).toBe(false); expect(response.componentName).toBe('SlideCard'); }); test('should generate JavaScript function from pattern', async () => { const params = { pattern: 'bounce-in', framework: 'js' as Framework, componentName: 'bounceAnimation', typescript: true }; const response = await tool.generateMotionComponent(params); expect(response.success).toBe(true); expect(response.code).toContain('import { animate } from \'motion\''); expect(response.code).toContain('function bounceAnimation'); expect(response.framework).toBe('js'); expect(response.componentName).toBe('bounceAnimation'); }); test('should handle custom animation properties', async () => { const params = { pattern: 'custom', framework: 'react' as Framework, customAnimation: { initial: { opacity: 0, rotate: -45 }, animate: { opacity: 1, rotate: 0 }, transition: { duration: 0.8, ease: 'backOut' } }, componentName: 'CustomComponent' }; const response = await tool.generateMotionComponent(params); expect(response.success).toBe(true); expect(response.code).toContain('opacity: 0'); expect(response.code).toContain('rotate: -45'); expect(response.code).toContain('duration: 0.8'); expect(response.code).toContain('ease: "backOut"'); }); test('should handle invalid pattern gracefully', async () => { const params = { pattern: 'non-existent-pattern', framework: 'react' as Framework }; const response = await tool.generateMotionComponent(params); expect(response.success).toBe(false); expect(response.error).toContain('Pattern not found'); }); test('should include performance analysis', async () => { const params = { pattern: 'fade-in', framework: 'react' as Framework, includeAnalysis: true }; const response = await tool.generateMotionComponent(params); expect(response.success).toBe(true); expect(response.analysis).toBeDefined(); expect(response.analysis?.performance).toBeDefined(); expect(response.analysis?.accessibility).toBeDefined(); expect(response.analysis?.bundleSize).toBeDefined(); }); }); describe('createAnimationSequence', () => { test('should create React animation sequence', async () => { const params = { framework: 'react' as Framework, steps: [ { selector: '.header', animation: { y: [-50, 0], opacity: [0, 1] }, timing: { duration: 0.6, delay: 0 } }, { selector: '.content', animation: { x: [-100, 0], opacity: [0, 1] }, timing: { duration: 0.8, delay: 0.3 } }, { selector: '.footer', animation: { y: [50, 0], opacity: [0, 1] }, timing: { duration: 0.5, delay: 0.6 } } ], stagger: { each: 0.1, from: 'first' }, componentName: 'SequenceComponent' }; const response = await tool.createAnimationSequence(params); expect(response.success).toBe(true); expect(response.code).toContain('AnimatePresence'); expect(response.code).toContain('staggerChildren'); expect(response.code).toContain('delayChildren'); expect(response.totalDuration).toBeGreaterThan(0); expect(response.stepCount).toBe(3); }); test('should create Vue animation sequence', async () => { const params = { framework: 'vue' as Framework, steps: [ { selector: '.item', animation: { scale: [0.8, 1] }, timing: { duration: 0.4 } } ], componentName: 'VueSequence' }; const response = await tool.createAnimationSequence(params); expect(response.success).toBe(true); expect(response.code).toContain('<transition-group'); expect(response.code).toContain('v-motion'); }); test('should create JavaScript animation sequence', async () => { const params = { framework: 'js' as Framework, steps: [ { selector: '.box', animation: { rotate: 360 }, timing: { duration: 1 } } ], componentName: 'jsSequence' }; const response = await tool.createAnimationSequence(params); expect(response.success).toBe(true); expect(response.code).toContain('timeline(['); expect(response.code).toContain('document.querySelector'); }); test('should handle empty steps', async () => { const params = { framework: 'react' as Framework, steps: [], componentName: 'EmptySequence' }; const response = await tool.createAnimationSequence(params); expect(response.success).toBe(false); expect(response.error).toContain('At least one animation step is required'); }); test('should calculate total duration correctly', async () => { const params = { framework: 'js' as Framework, steps: [ { selector: '.a', animation: { x: 100 }, timing: { duration: 0.5, delay: 0 } }, { selector: '.b', animation: { y: 100 }, timing: { duration: 0.8, delay: 0.3 } } ] }; const response = await tool.createAnimationSequence(params); expect(response.success).toBe(true); expect(response.totalDuration).toBe(1.1); // 0.3 (delay) + 0.8 (duration) }); }); describe('optimizeMotionCode', () => { test('should optimize React component code', async () => { const unoptimizedCode = ` import React from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { unused } from 'some-library'; const SlowComponent = () => { return ( <motion.div animate={{ x: [0, 100, 0, 100, 0] }} transition={{ duration: 5, repeat: Infinity }} > <motion.div animate={{ rotate: 360 }}> <motion.div animate={{ scale: [1, 2, 1] }}> Nested animations </motion.div> </motion.div> </motion.div> ); }; `; const params = { code: unoptimizedCode, framework: 'react' as Framework, optimizations: ['performance', 'bundle-size', 'accessibility'] }; const response = await tool.optimizeMotionCode(params); expect(response.success).toBe(true); expect(response.optimizedCode).toBeDefined(); expect(response.optimizations).toBeDefined(); expect(response.optimizations.length).toBeGreaterThan(0); expect(response.performanceGain).toBeGreaterThan(0); expect(response.bundleSizeReduction).toBeGreaterThan(0); }); test('should provide accessibility optimizations', async () => { const codeWithAccessibilityIssues = ` import { motion } from 'framer-motion'; const FlashyComponent = () => ( <motion.div animate={{ backgroundColor: ['red', 'green', 'blue'], scale: [1, 5, 1] }} transition={{ duration: 0.1, repeat: Infinity }} > Flashing content </motion.div> ); `; const params = { code: codeWithAccessibilityIssues, framework: 'react' as Framework, optimizations: ['accessibility'] }; const response = await tool.optimizeMotionCode(params); expect(response.success).toBe(true); expect(response.optimizedCode).toContain('prefers-reduced-motion'); expect(response.accessibilityImprovements).toBeDefined(); expect(response.accessibilityScore).toBeGreaterThan(0); }); test('should optimize bundle size', async () => { const heavyCode = ` import { motion, AnimatePresence, useAnimation, useMotionValue } from 'framer-motion'; // Only using motion.div but importing everything const SimpleComponent = () => ( <motion.div animate={{ x: 100 }}> Simple animation </motion.div> ); `; const params = { code: heavyCode, framework: 'react' as Framework, optimizations: ['bundle-size'] }; const response = await tool.optimizeMotionCode(params); expect(response.success).toBe(true); expect(response.bundleSizeReduction).toBeGreaterThan(0); expect(response.optimizations.some(opt => opt.includes('import'))).toBe(true); }); test('should handle invalid code gracefully', async () => { const invalidCode = 'This is not valid code {{{'; const params = { code: invalidCode, framework: 'react' as Framework }; const response = await tool.optimizeMotionCode(params); expect(response.success).toBe(false); expect(response.error).toContain('Failed to parse code'); }); }); describe('convertBetweenFrameworks', () => { test('should convert React to Vue', async () => { const reactCode = ` import React from 'react'; import { motion } from 'framer-motion'; const ReactComponent = () => { return ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} whileHover={{ scale: 1.1 }} > React content </motion.div> ); }; export default ReactComponent; `; const params = { code: reactCode, sourceFramework: 'react' as Framework, targetFramework: 'vue' as Framework, componentName: 'VueComponent' }; const response = await tool.convertBetweenFrameworks(params); expect(response.success).toBe(true); expect(response.convertedCode).toContain('<template>'); expect(response.convertedCode).toContain('<script>'); expect(response.convertedCode).toContain('v-motion'); expect(response.conversionNotes).toBeDefined(); expect(response.conversionNotes.length).toBeGreaterThan(0); }); test('should convert Vue to React', async () => { const vueCode = ` <template> <div v-motion :initial="{ scale: 0 }" :enter="{ scale: 1 }" class="vue-component" > Vue content </div> </template> <script setup> import { ref } from 'vue'; </script> `; const params = { code: vueCode, sourceFramework: 'vue' as Framework, targetFramework: 'react' as Framework, componentName: 'ReactComponent' }; const response = await tool.convertBetweenFrameworks(params); expect(response.success).toBe(true); expect(response.convertedCode).toContain('import React from \'react\''); expect(response.convertedCode).toContain('import { motion } from \'framer-motion\''); expect(response.convertedCode).toContain('motion.div'); expect(response.convertedCode).toContain('className="vue-component"'); }); test('should convert JavaScript to React', async () => { const jsCode = ` import { animate, spring } from 'motion'; function animateButton() { const button = document.querySelector('.button'); animate(button, { scale: [1, 1.2, 1] }, { duration: 0.3, easing: spring({ stiffness: 400 }) } ); } export { animateButton }; `; const params = { code: jsCode, sourceFramework: 'js' as Framework, targetFramework: 'react' as Framework, componentName: 'AnimatedButton' }; const response = await tool.convertBetweenFrameworks(params); expect(response.success).toBe(true); expect(response.convertedCode).toContain('const AnimatedButton'); expect(response.convertedCode).toContain('motion.button'); expect(response.convertedCode).toContain('whileTap'); }); test('should handle unsupported conversion', async () => { const params = { code: 'const test = () => {};', sourceFramework: 'react' as Framework, targetFramework: 'angular' as any, // Unsupported componentName: 'Test' }; const response = await tool.convertBetweenFrameworks(params); expect(response.success).toBe(false); expect(response.error).toContain('Unsupported target framework'); }); test('should provide conversion limitations', async () => { const complexCode = ` import React, { useCallback, useMemo } from 'react'; import { motion, useAnimation } from 'framer-motion'; const ComplexComponent = () => { const controls = useAnimation(); const complexLogic = useMemo(() => ({ x: 100 }), []); return <motion.div animate={controls}>Complex</motion.div>; }; `; const params = { code: complexCode, sourceFramework: 'react' as Framework, targetFramework: 'vue' as Framework }; const response = await tool.convertBetweenFrameworks(params); expect(response.success).toBe(true); expect(response.limitations).toBeDefined(); expect(response.limitations.length).toBeGreaterThan(0); expect(response.conversionNotes.some(note => note.includes('useAnimation') || note.includes('useMemo') )).toBe(true); }); }); describe('validateMotionSyntax', () => { test('should validate correct React Motion code', async () => { const validCode = ` import React from 'react'; import { motion } from 'framer-motion'; const ValidComponent = () => ( <motion.div initial={{ opacity: 0, x: -100 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: 100 }} transition={{ duration: 0.5, ease: 'easeOut' }} > Valid content </motion.div> ); `; const params = { code: validCode, framework: 'react' as Framework }; const response = await tool.validateMotionSyntax(params); expect(response.isValid).toBe(true); expect(response.errors).toHaveLength(0); expect(response.warnings).toBeDefined(); expect(response.suggestions).toBeDefined(); expect(response.score).toBeGreaterThan(80); }); test('should detect syntax errors', async () => { const invalidCode = ` import { motion } from 'framer-motion'; const InvalidComponent = () => ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1, invalidProperty: true }} transition={{ duration: "not-a-number" }} > Invalid content </motion.div> ); `; const params = { code: invalidCode, framework: 'react' as Framework }; const response = await tool.validateMotionSyntax(params); expect(response.isValid).toBe(false); expect(response.errors.length).toBeGreaterThan(0); expect(response.errors.some(error => error.includes('invalidProperty') || error.includes('duration') )).toBe(true); }); test('should provide best practice suggestions', async () => { const suboptimalCode = ` import { motion } from 'framer-motion'; const SuboptimalComponent = () => ( <motion.div animate={{ x: [0, 100, 200, 300, 400] }} transition={{ duration: 10, repeat: Infinity }} > Long animation </motion.div> ); `; const params = { code: suboptimalCode, framework: 'react' as Framework, includePerformanceCheck: true }; const response = await tool.validateMotionSyntax(params); expect(response.isValid).toBe(true); expect(response.warnings.length).toBeGreaterThan(0); expect(response.suggestions.length).toBeGreaterThan(0); expect(response.performanceScore).toBeLessThan(80); }); test('should validate Vue Motion syntax', async () => { const vueCode = ` <template> <div v-motion :initial="{ opacity: 0 }" :enter="{ opacity: 1 }" :leave="{ opacity: 0 }" > Vue motion content </div> </template> `; const params = { code: vueCode, framework: 'vue' as Framework }; const response = await tool.validateMotionSyntax(params); expect(response.isValid).toBe(true); expect(response.errors).toHaveLength(0); }); test('should validate JavaScript Motion syntax', async () => { const jsCode = ` import { animate, spring } from 'motion'; function validAnimation() { const element = document.querySelector('.target'); animate(element, { x: 100, opacity: 1 }, { duration: 0.5, easing: spring({ stiffness: 300, damping: 30 }) } ); } `; const params = { code: jsCode, framework: 'js' as Framework }; const response = await tool.validateMotionSyntax(params); expect(response.isValid).toBe(true); expect(response.score).toBeGreaterThan(70); }); test('should detect accessibility issues', async () => { const accessibilityProblematicCode = ` import { motion } from 'framer-motion'; const ProblematicComponent = () => ( <motion.div animate={{ backgroundColor: ['red', 'yellow', 'red'], scale: [1, 3, 1] }} transition={{ duration: 0.1, repeat: Infinity }} > Flashing content </motion.div> ); `; const params = { code: accessibilityProblematicCode, framework: 'react' as Framework, includeAccessibilityCheck: true }; const response = await tool.validateMotionSyntax(params); expect(response.accessibilityIssues).toBeDefined(); expect(response.accessibilityIssues.length).toBeGreaterThan(0); expect(response.accessibilityScore).toBeLessThan(50); }); }); describe('Error Handling', () => { test('should handle missing parameters gracefully', async () => { const response = await tool.generateMotionComponent({} as any); expect(response.success).toBe(false); expect(response.error).toContain('Missing required parameter'); }); test('should handle invalid frameworks', async () => { const params = { pattern: 'fade-in', framework: 'invalid' as Framework }; const response = await tool.generateMotionComponent(params); expect(response.success).toBe(false); expect(response.error).toContain('Unsupported framework'); }); test('should handle empty code input', async () => { const response = await tool.validateMotionSyntax({ code: '', framework: 'react' as Framework }); expect(response.isValid).toBe(false); expect(response.errors).toContain('Empty code provided'); }); }); describe('Integration and Performance', () => { test('should handle large code inputs efficiently', async () => { // Generate large component code const largeCode = ` import React from 'react'; import { motion } from 'framer-motion'; const LargeComponent = () => ( <div> ${Array.from({ length: 100 }, (_, i) => `<motion.div key={${i}} animate={{ x: ${i * 10} }}>Item ${i}</motion.div>` ).join('\n ')} </div> ); `; const startTime = Date.now(); const response = await tool.validateMotionSyntax({ code: largeCode, framework: 'react' as Framework }); const endTime = Date.now(); expect(response).toBeDefined(); expect(endTime - startTime).toBeLessThan(5000); // Should complete within 5 seconds }); test('should cache pattern results for performance', async () => { const params = { pattern: 'fade-in', framework: 'react' as Framework, componentName: 'CachedComponent' }; // First call const start1 = Date.now(); const response1 = await tool.generateMotionComponent(params); const end1 = Date.now(); // Second call (should be faster due to caching) const start2 = Date.now(); const response2 = await tool.generateMotionComponent(params); const end2 = Date.now(); expect(response1.success).toBe(true); expect(response2.success).toBe(true); expect(response1.code).toBe(response2.code); // Second call should be faster (cached) expect(end2 - start2).toBeLessThanOrEqual(end1 - start1); }); }); });

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/Abhishekrajpurohit/motion-dev-mcp'

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