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
"""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
from chuk_motion.generator.timeline import Timeline
# Mock ProjectManager with current_timeline
pm_mock = Mock()
timeline = Timeline(fps=30)
pm_mock.current_timeline = timeline
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
assert len(timeline.get_all_components()) >= 1
def test_tool_execution_all_params(self):
"""Test tool execution with all parameters."""
from chuk_motion.components.animations.LayoutEntrance.tool import register_tool
from chuk_motion.generator.timeline import Timeline
pm_mock = Mock()
timeline = Timeline(fps=30)
pm_mock.current_timeline = timeline
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,
)
)
result_data = json.loads(result)
assert result_data["component"] == "LayoutEntrance"
assert result_data["duration"] == 5.0
# Verify component was added
components = timeline.get_all_components()
assert len(components) >= 1
comp = components[0]
assert comp.component_type == "LayoutEntrance"
@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
from chuk_motion.generator.timeline import Timeline
pm_mock = Mock()
timeline = Timeline(fps=30)
pm_mock.current_timeline = timeline
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
from chuk_motion.generator.timeline import Timeline
pm_mock = Mock()
timeline = Timeline(fps=30)
pm_mock.current_timeline = timeline
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 unittest.mock import patch
from chuk_motion.components.animations.LayoutEntrance.tool import register_tool
from chuk_motion.generator.timeline import Timeline
# Mock ProjectManager with timeline that raises an error
pm_mock = Mock()
timeline = Timeline(fps=30)
pm_mock.current_timeline = timeline
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"}})
# Patch add_layout_entrance to raise an error
with patch.object(timeline, "add_layout_entrance", side_effect=Exception("Test error")):
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