Skip to main content
Glama
test_motion.py17.3 kB
# tests/tokens/test_motion.py """ Comprehensive tests for motion token system. """ import pytest from pydantic import ValidationError from chuk_motion.tokens.motion import ( MOTION_TOKENS, CTATiming, DurationConfig, EasingConfig, EnterTransition, ExitTransition, MotionTokens, PlatformTimingConfig, RemotionSpringConfig, SpringConfig, TempoConfig, TransitionProperties, get_duration, get_easing, get_enter_transition, get_exit_transition, get_platform_timing, get_spring, get_tempo, list_durations, list_easings, list_enter_transitions, list_exit_transitions, list_platforms, list_springs, list_tempos, ) class TestPydanticModels: """Test all Pydantic models are properly defined.""" def test_duration_config_model(self): """Test DurationConfig model validation.""" duration = DurationConfig( ms=350, frames_30fps=11, frames_60fps=21, seconds=0.35, css="0.35s", description="Test duration", ) assert duration.ms == 350 assert duration.frames_30fps == 11 assert duration.seconds == 0.35 def test_duration_config_validation_error(self): """Test DurationConfig validation fails with invalid data.""" with pytest.raises(ValidationError): DurationConfig(ms="invalid", frames_30fps=11, frames_60fps=21) def test_easing_config_model(self): """Test EasingConfig model validation.""" easing = EasingConfig( curve=[0.42, 0.0, 0.58, 1.0], css="ease-in-out", description="Test easing", usage="Test usage", ) assert len(easing.curve) == 4 assert easing.css == "ease-in-out" def test_remotion_spring_config_model(self): """Test RemotionSpringConfig model validation.""" spring = RemotionSpringConfig( damping=100.0, mass=1.0, stiffness=100.0, overshootClamping=False ) assert spring.damping == 100.0 assert spring.mass == 1.0 assert spring.stiffness == 100.0 assert spring.overshootClamping is False def test_spring_config_model(self): """Test SpringConfig with nested RemotionSpringConfig.""" config = RemotionSpringConfig( damping=50.0, mass=1.0, stiffness=120.0, overshootClamping=False ) spring = SpringConfig( config=config, description="Smooth spring", feel="Natural", usage="General purpose", ) assert spring.config.damping == 50.0 assert spring.feel == "Natural" def test_transition_properties_model(self): """Test TransitionProperties with alias support.""" props = TransitionProperties(**{"from": 0, "to": 1}) assert props.from_value == 0 assert props.to == 1 def test_enter_transition_model(self): """Test EnterTransition model.""" transition = EnterTransition( properties={ "opacity": TransitionProperties(**{"from": 0, "to": 1}), }, description="Fade in", usage="Subtle entrances", default_duration="normal", default_easing="ease_out", ) assert "opacity" in transition.properties assert transition.default_duration == "normal" def test_cta_timing_model(self): """Test CTATiming model.""" cta = CTATiming(first_cta=5.0, final_cta=-3.0, mid_roll_cta=180.0) assert cta.first_cta == 5.0 assert cta.final_cta == -3.0 assert cta.mid_roll_cta == 180.0 def test_cta_timing_optional_field(self): """Test CTATiming with optional mid_roll_cta.""" cta = CTATiming(first_cta=5.0, final_cta=-3.0) assert cta.mid_roll_cta is None def test_platform_timing_config_model(self): """Test PlatformTimingConfig with CTATiming.""" timing = PlatformTimingConfig( hook_duration=2.0, scene_change_interval=3.0, caption_display_duration=1.0, cta_timing=CTATiming(first_cta=5.0, final_cta=-3.0), attention_span="short", recommended_tempo="fast", description="Test timing", ) assert timing.hook_duration == 2.0 assert timing.cta_timing.first_cta == 5.0 class TestMotionTokensStructure: """Test MOTION_TOKENS structure and completeness.""" def test_motion_tokens_is_pydantic_model(self): """Test that MOTION_TOKENS is a Pydantic model instance.""" assert isinstance(MOTION_TOKENS, MotionTokens) def test_all_duration_tokens_present(self): """Test all expected duration tokens exist.""" expected_durations = [ "instant", "ultra_fast", "fast", "normal", "medium", "slow", "slower", "ultra_slow", ] for duration in expected_durations: assert duration in MOTION_TOKENS.duration, f"Duration '{duration}' not found" assert isinstance(MOTION_TOKENS.duration[duration], DurationConfig) def test_duration_frame_calculations(self): """Test duration frame calculations are correct.""" normal = MOTION_TOKENS.duration["normal"] assert normal.ms == 350 assert normal.frames_30fps == 11 # 350ms / 33.33ms per frame ≈ 11 assert normal.frames_60fps == 21 # 350ms / 16.67ms per frame ≈ 21 assert normal.seconds == 0.35 def test_all_easing_tokens_present(self): """Test all expected easing tokens exist.""" expected_easings = [ "linear", "ease_in_out", "ease_out", "ease_in", "ease_out_back", "ease_out_expo", ] for easing in expected_easings: assert easing in MOTION_TOKENS.easing, f"Easing '{easing}' not found" assert isinstance(MOTION_TOKENS.easing[easing], EasingConfig) def test_easing_curve_format(self): """Test easing curves have correct format.""" for easing_name, easing in MOTION_TOKENS.easing.items(): assert len(easing.curve) == 4, f"Easing '{easing_name}' curve should have 4 values" assert all(isinstance(v, (int, float)) for v in easing.curve), ( f"Easing '{easing_name}' curve values should be numeric" ) def test_all_spring_configs_present(self): """Test all expected spring configs exist.""" expected_springs = ["gentle", "smooth", "bouncy", "snappy", "wobbly", "stiff", "explosive"] for spring in expected_springs: assert spring in MOTION_TOKENS.spring_configs, f"Spring '{spring}' not found" assert isinstance(MOTION_TOKENS.spring_configs[spring], SpringConfig) def test_spring_config_structure(self): """Test spring configs have proper structure.""" smooth = MOTION_TOKENS.spring_configs["smooth"] assert isinstance(smooth.config, RemotionSpringConfig) assert smooth.config.damping > 0 assert smooth.config.mass > 0 assert smooth.config.stiffness > 0 assert isinstance(smooth.config.overshootClamping, bool) def test_all_enter_transitions_present(self): """Test all expected enter transitions exist.""" expected_enters = [ "fade_in", "fade_up", "fade_down", "scale_in", "zoom_in", "slide_in_left", "slide_in_right", "bounce_in", "rotate_in", "blur_in", ] for enter in expected_enters: assert enter in MOTION_TOKENS.enter, f"Enter transition '{enter}' not found" assert isinstance(MOTION_TOKENS.enter[enter], EnterTransition) def test_all_exit_transitions_present(self): """Test all expected exit transitions exist.""" expected_exits = [ "fade_out", "fade_out_down", "fade_out_up", "scale_out", "zoom_out", "slide_out_left", "slide_out_right", "blur_out", ] for exit_trans in expected_exits: assert exit_trans in MOTION_TOKENS.exit, f"Exit transition '{exit_trans}' not found" assert isinstance(MOTION_TOKENS.exit[exit_trans], ExitTransition) def test_all_tempo_tokens_present(self): """Test all expected tempo tokens exist.""" expected_tempos = ["sprint", "fast", "medium", "slow", "cinematic"] for tempo in expected_tempos: assert tempo in MOTION_TOKENS.tempo, f"Tempo '{tempo}' not found" assert isinstance(MOTION_TOKENS.tempo[tempo], TempoConfig) def test_tempo_beat_calculations(self): """Test tempo beat calculations are consistent.""" for tempo_name, tempo in MOTION_TOKENS.tempo.items(): # Verify frame calculations match beat duration expected_30fps = int(tempo.beat_duration * 30) expected_60fps = int(tempo.beat_duration * 60) assert tempo.frames_30fps == expected_30fps, ( f"Tempo '{tempo_name}' 30fps frames mismatch" ) assert tempo.frames_60fps == expected_60fps, ( f"Tempo '{tempo_name}' 60fps frames mismatch" ) def test_all_platform_timings_present(self): """Test all expected platform timings exist.""" expected_platforms = [ "tiktok", "youtube_shorts", "instagram_reel", "youtube_long_form", "linkedin", "presentation", ] for platform in expected_platforms: assert platform in MOTION_TOKENS.platform_timing, f"Platform '{platform}' not found" assert isinstance(MOTION_TOKENS.platform_timing[platform], PlatformTimingConfig) def test_platform_timing_cta_structure(self): """Test platform timing CTA timing is properly structured.""" for platform, timing in MOTION_TOKENS.platform_timing.items(): assert isinstance(timing.cta_timing, CTATiming) assert timing.cta_timing.first_cta > 0, ( f"Platform '{platform}' first_cta should be positive" ) assert timing.cta_timing.final_cta < 0, ( f"Platform '{platform}' final_cta should be negative" ) class TestUtilityFunctions: """Test all utility functions.""" def test_get_duration(self): """Test get_duration utility function.""" duration = get_duration("normal") assert isinstance(duration, DurationConfig) assert duration.ms == 350 def test_get_duration_fallback(self): """Test get_duration falls back to 'normal' for unknown durations.""" duration = get_duration("unknown_duration") assert duration.ms == 350 # Should return 'normal' def test_get_easing(self): """Test get_easing utility function.""" easing = get_easing("ease_out") assert isinstance(easing, EasingConfig) assert easing.css == "ease-out" def test_get_easing_fallback(self): """Test get_easing falls back to 'ease_out' for unknown easings.""" easing = get_easing("unknown_easing") assert easing.css == "ease-out" def test_get_spring(self): """Test get_spring utility function.""" spring = get_spring("smooth") assert isinstance(spring, SpringConfig) assert spring.config.damping == 50 def test_get_spring_fallback(self): """Test get_spring falls back to 'smooth' for unknown springs.""" spring = get_spring("unknown_spring") assert spring.config.damping == 50 def test_get_enter_transition(self): """Test get_enter_transition utility function.""" transition = get_enter_transition("fade_in") assert isinstance(transition, EnterTransition) assert "opacity" in transition.properties def test_get_exit_transition(self): """Test get_exit_transition utility function.""" transition = get_exit_transition("fade_out") assert isinstance(transition, ExitTransition) assert "opacity" in transition.properties def test_get_tempo(self): """Test get_tempo utility function.""" tempo = get_tempo("medium") assert isinstance(tempo, TempoConfig) assert tempo.beat_duration == 2.0 def test_get_platform_timing(self): """Test get_platform_timing utility function.""" timing = get_platform_timing("tiktok") assert isinstance(timing, PlatformTimingConfig) assert timing.attention_span == "ultra_short" def test_list_durations(self): """Test list_durations returns all duration names.""" durations = list_durations() assert isinstance(durations, list) assert "normal" in durations assert "fast" in durations assert len(durations) == 8 def test_list_easings(self): """Test list_easings returns all easing names.""" easings = list_easings() assert isinstance(easings, list) assert "ease_out" in easings assert len(easings) >= 10 def test_list_springs(self): """Test list_springs returns all spring config names.""" springs = list_springs() assert isinstance(springs, list) assert "smooth" in springs assert len(springs) == 7 def test_list_enter_transitions(self): """Test list_enter_transitions returns all enter transition names.""" enters = list_enter_transitions() assert isinstance(enters, list) assert "fade_in" in enters assert len(enters) == 10 def test_list_exit_transitions(self): """Test list_exit_transitions returns all exit transition names.""" exits = list_exit_transitions() assert isinstance(exits, list) assert "fade_out" in exits assert len(exits) == 8 def test_list_tempos(self): """Test list_tempos returns all tempo names.""" tempos = list_tempos() assert isinstance(tempos, list) assert "medium" in tempos assert len(tempos) == 5 def test_list_platforms(self): """Test list_platforms returns all platform names.""" platforms = list_platforms() assert isinstance(platforms, list) assert "tiktok" in platforms assert "youtube_long_form" in platforms assert len(platforms) == 6 class TestTokenConsistency: """Test consistency across motion tokens.""" def test_transition_durations_reference_valid_tokens(self): """Test that transitions reference valid duration tokens.""" for enter_name, enter in MOTION_TOKENS.enter.items(): assert enter.default_duration in MOTION_TOKENS.duration, ( f"Enter '{enter_name}' references invalid duration '{enter.default_duration}'" ) for exit_name, exit_trans in MOTION_TOKENS.exit.items(): assert exit_trans.default_duration in MOTION_TOKENS.duration, ( f"Exit '{exit_name}' references invalid duration '{exit_trans.default_duration}'" ) def test_transition_easings_reference_valid_tokens(self): """Test that transitions reference valid easing tokens.""" for enter_name, enter in MOTION_TOKENS.enter.items(): assert enter.default_easing in MOTION_TOKENS.easing, ( f"Enter '{enter_name}' references invalid easing '{enter.default_easing}'" ) for exit_name, exit_trans in MOTION_TOKENS.exit.items(): assert exit_trans.default_easing in MOTION_TOKENS.easing, ( f"Exit '{exit_name}' references invalid easing '{exit_trans.default_easing}'" ) def test_platform_tempo_references_valid_tokens(self): """Test that platform timings reference valid tempo tokens.""" for platform, timing in MOTION_TOKENS.platform_timing.items(): assert timing.recommended_tempo in MOTION_TOKENS.tempo, ( f"Platform '{platform}' references invalid tempo '{timing.recommended_tempo}'" ) def test_tempo_progression(self): """Test that tempos have logical progression.""" sprint = MOTION_TOKENS.tempo["sprint"] fast = MOTION_TOKENS.tempo["fast"] medium = MOTION_TOKENS.tempo["medium"] slow = MOTION_TOKENS.tempo["slow"] cinematic = MOTION_TOKENS.tempo["cinematic"] # Beat durations should increase assert sprint.beat_duration < fast.beat_duration assert fast.beat_duration < medium.beat_duration assert medium.beat_duration < slow.beat_duration assert slow.beat_duration < cinematic.beat_duration # Cuts per minute should decrease assert sprint.cuts_per_minute > fast.cuts_per_minute assert fast.cuts_per_minute > medium.cuts_per_minute assert medium.cuts_per_minute > slow.cuts_per_minute

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