Skip to main content
Glama
test_layoutentrance.py16.8 kB
"""Tests for LayoutEntrance component.""" import asyncio import json from unittest.mock import Mock import pytest from tests.components.conftest import ( assert_has_interface, assert_has_timing_props, assert_has_visibility_check, assert_valid_typescript, ) class TestLayoutEntranceBasic: """Basic LayoutEntrance generation tests.""" def test_basic_generation(self, component_builder, theme_name): """Test basic LayoutEntrance generation.""" tsx = component_builder.build_component("LayoutEntrance", {}, theme_name) assert tsx is not None assert "LayoutEntrance" in tsx assert_valid_typescript(tsx) assert_has_interface(tsx, "LayoutEntrance") assert_has_timing_props(tsx) assert_has_visibility_check(tsx) class TestLayoutEntranceBuilderMethod: """Tests for LayoutEntrance builder method.""" def test_add_to_composition_basic(self): """Test add_to_composition creates ComponentInstance.""" from chuk_motion.components.animations.LayoutEntrance.builder import ( add_to_composition, ) from chuk_motion.generator.composition_builder import CompositionBuilder builder = CompositionBuilder() result = add_to_composition(builder, start_time=0.0) assert result is builder assert len(builder.components) == 1 assert builder.components[0].component_type == "LayoutEntrance" def test_add_to_composition_all_props(self): """Test all props are set correctly.""" from chuk_motion.components.animations.LayoutEntrance.builder import ( add_to_composition, ) from chuk_motion.generator.composition_builder import CompositionBuilder builder = CompositionBuilder() add_to_composition( builder, start_time=1.0, content={"type": "Grid"}, entrance_type="fade_slide_up", entrance_delay=0.5, duration=10.0, ) props = builder.components[0].props assert props["content"] == {"type": "Grid"} assert props["entrance_type"] == "fade_slide_up" assert props["entrance_delay"] == 0.5 def test_add_to_composition_timing(self): """Test add_to_composition handles timing correctly.""" from chuk_motion.components.animations.LayoutEntrance.builder import ( add_to_composition, ) from chuk_motion.generator.composition_builder import CompositionBuilder builder = CompositionBuilder(fps=30) add_to_composition(builder, start_time=2.0, duration=5.0) component = builder.components[0] assert component.start_frame == 60 assert component.duration_frames == 150 class TestLayoutEntranceToolRegistration: """Tests for LayoutEntrance MCP tool registration.""" def test_register_tool(self): """Test tool registration.""" from chuk_motion.components.animations.LayoutEntrance.tool import register_tool mcp_mock = Mock() pm_mock = Mock() register_tool(mcp_mock, pm_mock) mcp_mock.tool.assert_called_once() def test_tool_execution_basic(self): """Test basic tool execution with valid content.""" from chuk_motion.components.animations.LayoutEntrance.tool import register_tool # Mock ProjectManager with current_timeline pm_mock = Mock() timeline_mock = Mock() component_mock = Mock() component_mock.start_frame = 0 timeline_mock.add_component = Mock(return_value=component_mock) timeline_mock.frames_to_seconds = Mock(return_value=0.0) pm_mock.current_timeline = timeline_mock mcp_mock = Mock() register_tool(mcp_mock, pm_mock) tool_func = mcp_mock.tool.call_args[0][0] # Create valid content content = json.dumps({"type": "Grid", "config": {"layout": "3x3"}}) result = asyncio.run(tool_func(content=content)) result_data = json.loads(result) assert result_data["component"] == "LayoutEntrance" # Verify component was added timeline_mock.add_component.assert_called_once() def test_tool_execution_all_params(self): """Test tool execution with all parameters.""" from chuk_motion.components.animations.LayoutEntrance.tool import register_tool pm_mock = Mock() timeline_mock = Mock() component_mock = Mock() component_mock.start_frame = 60 timeline_mock.add_component = Mock(return_value=component_mock) timeline_mock.frames_to_seconds = Mock(return_value=2.0) pm_mock.current_timeline = timeline_mock mcp_mock = Mock() register_tool(mcp_mock, pm_mock) tool_func = mcp_mock.tool.call_args[0][0] content = json.dumps({"type": "Container", "config": {"position": "center"}}) result = asyncio.run( tool_func( content=content, entrance_type="scale_in_pop", entrance_delay=0.5, duration=5.0, track="overlay", gap_before=1.0, ) ) result_data = json.loads(result) assert result_data["component"] == "LayoutEntrance" assert result_data["start_time"] == 2.0 # Verify component was added with correct params call_args = timeline_mock.add_component.call_args assert call_args[1]["duration"] == 5.0 assert call_args[1]["track"] == "overlay" assert call_args[1]["gap_before"] == 1.0 @pytest.mark.parametrize( "entrance_type", [ "none", "fade_in", "fade_slide_up", "scale_in_soft", "scale_in_pop", "slide_in_left", "slide_in_right", "blur_in", "zoom_in", ], ) def test_tool_execution_entrance_types(self, entrance_type): """Test tool execution with different entrance types.""" from chuk_motion.components.animations.LayoutEntrance.tool import register_tool pm_mock = Mock() timeline_mock = Mock() component_mock = Mock() component_mock.start_frame = 0 timeline_mock.add_component = Mock(return_value=component_mock) timeline_mock.frames_to_seconds = Mock(return_value=0.0) pm_mock.current_timeline = timeline_mock mcp_mock = Mock() register_tool(mcp_mock, pm_mock) tool_func = mcp_mock.tool.call_args[0][0] content = json.dumps({"type": "TitleScene", "config": {"text": "Hello"}}) result = asyncio.run( tool_func( content=content, entrance_type=entrance_type, ) ) result_data = json.loads(result) assert result_data["component"] == "LayoutEntrance" def test_tool_execution_invalid_entrance_type(self): """Test tool execution with invalid entrance type.""" from chuk_motion.components.animations.LayoutEntrance.tool import register_tool pm_mock = Mock() timeline_mock = Mock() pm_mock.current_timeline = timeline_mock mcp_mock = Mock() register_tool(mcp_mock, pm_mock) tool_func = mcp_mock.tool.call_args[0][0] content = json.dumps({"type": "TitleScene", "config": {"text": "Hello"}}) result = asyncio.run( tool_func( content=content, entrance_type="invalid_type", ) ) result_data = json.loads(result) assert "error" in result_data assert "Invalid entrance_type" in result_data["error"] def test_tool_execution_invalid_json_content(self): """Test tool execution with invalid JSON in content.""" from chuk_motion.components.animations.LayoutEntrance.tool import register_tool pm_mock = Mock() timeline_mock = Mock() pm_mock.current_timeline = timeline_mock mcp_mock = Mock() register_tool(mcp_mock, pm_mock) tool_func = mcp_mock.tool.call_args[0][0] result = asyncio.run( tool_func( content="invalid json", ) ) result_data = json.loads(result) assert "error" in result_data assert "Invalid JSON" in result_data["error"] def test_tool_execution_invalid_content_format(self): """Test tool execution with invalid content format.""" from chuk_motion.components.animations.LayoutEntrance.tool import register_tool pm_mock = Mock() timeline_mock = Mock() component_mock = Mock() component_mock.start_frame = 0 timeline_mock.add_component = Mock(return_value=component_mock) timeline_mock.frames_to_seconds = Mock(return_value=0.0) pm_mock.current_timeline = timeline_mock mcp_mock = Mock() register_tool(mcp_mock, pm_mock) tool_func = mcp_mock.tool.call_args[0][0] # Missing 'type' key content = json.dumps({"config": {"text": "Missing type"}}) result = asyncio.run(tool_func(content=content)) result_data = json.loads(result) assert "error" in result_data assert "Invalid content format" in result_data["error"] def test_tool_execution_no_project(self): """Test tool execution without active project.""" from chuk_motion.components.animations.LayoutEntrance.tool import register_tool # Mock ProjectManager with no current_timeline pm_mock = Mock() pm_mock.current_timeline = None mcp_mock = Mock() register_tool(mcp_mock, pm_mock) tool_func = mcp_mock.tool.call_args[0][0] content = json.dumps({"type": "TitleScene", "config": {"text": "Hello"}}) result = asyncio.run(tool_func(content=content)) result_data = json.loads(result) assert "error" in result_data assert "No active project" in result_data["error"] def test_tool_execution_error_handling(self): """Test tool handles errors gracefully.""" from chuk_motion.components.animations.LayoutEntrance.tool import register_tool # Mock ProjectManager with timeline that raises an error pm_mock = Mock() timeline_mock = Mock() timeline_mock.add_component = Mock(side_effect=Exception("Test error")) pm_mock.current_timeline = timeline_mock mcp_mock = Mock() register_tool(mcp_mock, pm_mock) tool_func = mcp_mock.tool.call_args[0][0] content = json.dumps({"type": "TitleScene", "config": {"text": "Hello"}}) result = asyncio.run(tool_func(content=content)) result_data = json.loads(result) assert "error" in result_data assert "Test error" in result_data["error"] class TestLayoutEntranceRenderer: """Tests for LayoutEntrance custom JSX renderer.""" def test_render_jsx_with_content(self): """Test render_jsx with valid content ComponentInstance.""" from chuk_motion.components.animations.LayoutEntrance.renderer import render_jsx from chuk_motion.generator.composition_builder import ComponentInstance # Create nested content content = ComponentInstance( component_type="Grid", start_frame=0, duration_frames=150, props={"layout": "3x3"}, layer=0, ) # Create LayoutEntrance component with content comp = ComponentInstance( component_type="LayoutEntrance", start_frame=0, duration_frames=150, props={"content": content, "entranceType": "fade_in"}, layer=0, ) # Mock functions def mock_render_child(child, indent): return f"{' ' * indent}<Grid />" def mock_snake_to_camel(s): parts = s.split("_") return parts[0] + "".join(p.capitalize() for p in parts[1:]) def mock_format_prop_value(v): if isinstance(v, str): return f'"{v}"' return f"{{{v}}}" result = render_jsx( comp, mock_render_child, 0, mock_snake_to_camel, mock_format_prop_value, ) assert result is not None assert "LayoutEntrance" in result assert "entranceType" in result assert "<Grid />" in result def test_render_jsx_no_content(self): """Test render_jsx returns None when no content.""" from chuk_motion.components.animations.LayoutEntrance.renderer import render_jsx from chuk_motion.generator.composition_builder import ComponentInstance # Create LayoutEntrance without content comp = ComponentInstance( component_type="LayoutEntrance", start_frame=0, duration_frames=150, props={"entranceType": "fade_in"}, layer=0, ) result = render_jsx(comp, None, 0, None, None) assert result is None def test_render_jsx_content_not_component_instance(self): """Test render_jsx returns None when content is not ComponentInstance.""" from chuk_motion.components.animations.LayoutEntrance.renderer import render_jsx from chuk_motion.generator.composition_builder import ComponentInstance # Create LayoutEntrance with invalid content (not ComponentInstance) comp = ComponentInstance( component_type="LayoutEntrance", start_frame=0, duration_frames=150, props={"content": {"type": "Grid"}, "entranceType": "fade_in"}, layer=0, ) result = render_jsx(comp, None, 0, None, None) assert result is None def test_render_jsx_with_props(self): """Test render_jsx with multiple props.""" from chuk_motion.components.animations.LayoutEntrance.renderer import render_jsx from chuk_motion.generator.composition_builder import ComponentInstance # Create nested content content = ComponentInstance( component_type="Container", start_frame=0, duration_frames=150, props={}, layer=0, ) # Create LayoutEntrance with multiple props comp = ComponentInstance( component_type="LayoutEntrance", start_frame=0, duration_frames=150, props={ "content": content, "entranceType": "scale_in_pop", "entranceDelay": 0.5, }, layer=0, ) def mock_render_child(child, indent): return f"{' ' * indent}<Container />" def mock_snake_to_camel(s): parts = s.split("_") return parts[0] + "".join(p.capitalize() for p in parts[1:]) def mock_format_prop_value(v): if isinstance(v, str): return f'"{v}"' return f"{{{v}}}" result = render_jsx( comp, mock_render_child, 0, mock_snake_to_camel, mock_format_prop_value, ) assert result is not None assert "entranceType" in result assert "entranceDelay" in result assert "<Container />" in result def test_render_jsx_without_props(self): """Test render_jsx with no props besides content.""" from chuk_motion.components.animations.LayoutEntrance.renderer import render_jsx from chuk_motion.generator.composition_builder import ComponentInstance # Create nested content content = ComponentInstance( component_type="Grid", start_frame=0, duration_frames=150, props={}, layer=0, ) # Create LayoutEntrance with only content comp = ComponentInstance( component_type="LayoutEntrance", start_frame=0, duration_frames=150, props={"content": content}, layer=0, ) def mock_render_child(child, indent): return f"{' ' * indent}<Grid />" def mock_snake_to_camel(s): return s def mock_format_prop_value(v): return f"{{{v}}}" result = render_jsx( comp, mock_render_child, 0, mock_snake_to_camel, mock_format_prop_value, ) assert result is not None assert "LayoutEntrance" in result assert "startFrame" in result assert "durationInFrames" in result assert "<Grid />" in result

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/chrishayuk/chuk-mcp-remotion'

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