UNPKG

v0-rails

Version:

Convert React/JSX + Tailwind UI code from v0.dev to Rails ViewComponent classes and ERB templates with automatic slot detection, icon handling, and route generation

122 lines (110 loc) 3.03 kB
const { pascalCase } = require('../utils/string-utils'); /** * Generate component test from IR * @param {Object} ir - Intermediate Representation * @param {string} namespace - Ruby module namespace * @returns {string} - Component test code */ function generateComponentTest(ir, namespace) { const className = pascalCase(ir.name); const moduleNamespace = pascalCase(namespace); // Generate test props based on the component props const testProps = generateTestProps(ir.props); return `# frozen_string_literal: true require "test_helper" module ${moduleNamespace} class ${className}ComponentTest < ViewComponent::TestCase def test_component_renders # Arrange ${testProps} # Act render_inline(${moduleNamespace}::${className}Component.new(${generateTestPropsParams(ir.props)})) # Assert assert_selector("${getTestSelector(ir)}") assert_matches_snapshot end end end `; } /** * Generate test props for the component test * @param {Array} props - Props information * @returns {string} - Test props setup code */ function generateTestProps(props) { if (!props || props.length === 0) { return '# No props required'; } return props.map(prop => { // Skip rest props if (prop.isRest) { return `# Rest props: ${prop.name}`; } // Generate value based on prop type let value; switch (prop.type) { case 'string': value = `"Test ${prop.name}"`; break; case 'number': value = '42'; break; case 'boolean': value = 'true'; break; case 'array': value = '[]'; break; case 'object': value = '{}'; break; default: if (prop.defaultValue) { value = prop.defaultValue; } else { value = 'nil'; } } return `${prop.name} = ${value}`; }).join('\n '); } /** * Generate test props parameters for component initialization * @param {Array} props - Props information * @returns {string} - Test props parameters */ function generateTestPropsParams(props) { if (!props || props.length === 0) { return ''; } const paramStrings = props.map(prop => { // Handle rest props differently if (prop.isRest) { return ''; } return `${prop.name}: ${prop.name}`; }).filter(Boolean); return paramStrings.join(', '); } /** * Generate test selector for the component test * @param {Object} ir - Intermediate Representation * @returns {string} - CSS selector for the component test */ function getTestSelector(ir) { // Try to determine a reasonable selector from the HTML // This is a heuristic and might not always work perfectly // Check if we have a div or other common element as the root const rootElementMatch = ir.html.match(/^<([a-z][a-z0-9]*)/i); if (rootElementMatch) { return rootElementMatch[1]; } // Fallback to a generic selector return "*"; } module.exports = { generateComponentTest };