Skip to main content
Glama
test_captions.py17.8 kB
# tests/tokens/test_captions.py """ Comprehensive tests for caption style system. """ import pytest from pydantic import ValidationError from chuk_motion.tokens.captions import ( CAPTION_STYLES, AnimationConfig, BackgroundConfig, CaptionStyle, ColorsConfig, HighlightConfig, PositionConfig, TypographyConfig, get_caption_style, get_style_for_platform, list_caption_styles, ) class TestPydanticModels: """Test all Pydantic models are properly defined.""" def test_typography_config_model(self): """Test TypographyConfig model validation.""" typography = TypographyConfig( font_family=["Inter", "sans-serif"], font_weight=700, font_size=42, text_transform="uppercase", letter_spacing="0.05em", line_height=1.2, ) assert typography.font_weight == 700 assert typography.text_transform == "uppercase" def test_typography_config_literal_validation(self): """Test TypographyConfig rejects invalid text_transform.""" with pytest.raises(ValidationError): TypographyConfig( font_family=["Inter"], font_weight=700, font_size=42, text_transform="invalid", # Should fail letter_spacing="0.05em", line_height=1.2, ) def test_colors_config_model(self): """Test ColorsConfig model validation.""" colors = ColorsConfig( text="#ffffff", stroke="#000000", stroke_width=8, shadow="0 4px 8px rgba(0,0,0,0.8)" ) assert colors.text == "#ffffff" assert colors.stroke_width == 8 def test_colors_config_optional_stroke(self): """Test ColorsConfig with optional stroke.""" colors = ColorsConfig( text="#ffffff", stroke=None, stroke_width=0, shadow="0 2px 4px rgba(0,0,0,0.3)" ) assert colors.stroke is None def test_background_config_model(self): """Test BackgroundConfig model validation.""" background = BackgroundConfig( enabled=True, style="pill", color="rgba(0, 0, 0, 0.8)", padding="8px 24px", border_radius="1000px", blur=0, ) assert background.enabled is True assert background.style == "pill" def test_background_config_literal_validation(self): """Test BackgroundConfig rejects invalid style.""" with pytest.raises(ValidationError): BackgroundConfig( enabled=True, style="invalid", # Should fail color="rgba(0, 0, 0, 0.8)", padding="8px", border_radius="8px", ) def test_position_config_model(self): """Test PositionConfig model validation.""" position = PositionConfig(vertical="center", horizontal="center", offset_y=0) assert position.vertical == "center" assert position.horizontal == "center" def test_position_config_literal_validation(self): """Test PositionConfig rejects invalid positions.""" with pytest.raises(ValidationError): PositionConfig(vertical="invalid", horizontal="center") def test_animation_config_model(self): """Test AnimationConfig model validation.""" animation = AnimationConfig( enter="scale_in", exit="scale_out", enter_duration=0.15, exit_duration=0.1, scale_emphasis=1.15, ) assert animation.enter == "scale_in" assert animation.scale_emphasis == 1.15 def test_highlight_config_model(self): """Test HighlightConfig model validation.""" highlight = HighlightConfig( enabled=True, trigger="emphasis", color="#ffff00", background="rgba(255, 255, 0, 0.3)", scale=1.2, animation="bounce", ) assert highlight.enabled is True assert highlight.trigger == "emphasis" def test_highlight_config_literal_validation(self): """Test HighlightConfig rejects invalid trigger.""" with pytest.raises(ValidationError): HighlightConfig( enabled=True, trigger="invalid", # Should fail color="#ffff00", background="none", scale=1.0, animation="none", ) def test_caption_style_model(self): """Test complete CaptionStyle model validation.""" style = CaptionStyle( name="test", display_name="Test Style", description="Test description", display_mode="word_by_word", words_per_burst=1, word_duration=0.3, gap_duration=0.05, typography=TypographyConfig( font_family=["Inter"], font_weight=700, font_size=42, text_transform="none", letter_spacing="0.02em", line_height=1.4, ), colors=ColorsConfig( text="#ffffff", stroke="#000000", stroke_width=3, shadow="0 2px 4px rgba(0,0,0,0.5)" ), background=BackgroundConfig( enabled=True, style="box", color="rgba(0, 0, 0, 0.85)", padding="12px 24px", border_radius="8px", blur=4, ), position=PositionConfig(vertical="bottom", horizontal="center", offset_y=120), animation=AnimationConfig( enter="fade_up", exit="fade_out", enter_duration=0.3, exit_duration=0.2, scale_emphasis=1.0, ), highlight=HighlightConfig( enabled=True, trigger="keywords", color="#ffd700", background="none", scale=1.0, animation="none", ), platform_optimized=["youtube_long_form", "presentation"], recommended_tempo="medium", ) assert style.name == "test" assert style.display_mode == "word_by_word" class TestCaptionStylesStructure: """Test CAPTION_STYLES structure and completeness.""" def test_all_caption_styles_present(self): """Test all expected caption styles exist.""" expected_styles = ["burst", "precise", "headline", "minimal", "neon", "classic"] for style in expected_styles: assert style in CAPTION_STYLES, f"Caption style '{style}' not found" assert isinstance(CAPTION_STYLES[style], CaptionStyle) def test_style_names_match_keys(self): """Test that caption style names match their dictionary keys.""" for key, style in CAPTION_STYLES.items(): assert style.name == key, f"Caption style key '{key}' doesn't match name '{style.name}'" def test_all_styles_have_display_modes(self): """Test all styles have valid display modes.""" valid_modes = ["word_by_word", "phrase_by_phrase", "line_by_line", "full_sentence"] for style_name, style in CAPTION_STYLES.items(): assert style.display_mode in valid_modes, ( f"Style '{style_name}' has invalid display mode '{style.display_mode}'" ) def test_word_by_word_styles_have_word_duration(self): """Test word_by_word styles have word_duration set.""" for style_name, style in CAPTION_STYLES.items(): if style.display_mode == "word_by_word": assert style.word_duration is not None, ( f"Style '{style_name}' is word_by_word but missing word_duration" ) assert style.words_per_burst is not None, ( f"Style '{style_name}' is word_by_word but missing words_per_burst" ) def test_phrase_by_phrase_styles_have_phrase_duration(self): """Test phrase_by_phrase styles have phrase_duration set.""" for style_name, style in CAPTION_STYLES.items(): if style.display_mode == "phrase_by_phrase": assert style.phrase_duration is not None, ( f"Style '{style_name}' is phrase_by_phrase but missing phrase_duration" ) assert style.words_per_phrase is not None, ( f"Style '{style_name}' is phrase_by_phrase but missing words_per_phrase" ) def test_all_styles_have_typography(self): """Test all styles have typography configuration.""" for _style_name, style in CAPTION_STYLES.items(): assert isinstance(style.typography, TypographyConfig) assert len(style.typography.font_family) > 0 assert style.typography.font_size > 0 assert style.typography.font_weight >= 100 def test_all_styles_have_colors(self): """Test all styles have color configuration.""" for style_name, style in CAPTION_STYLES.items(): assert isinstance(style.colors, ColorsConfig) assert style.colors.text, f"Style '{style_name}' missing text color" assert style.colors.shadow, f"Style '{style_name}' missing shadow" def test_all_styles_have_position(self): """Test all styles have position configuration.""" for _style_name, style in CAPTION_STYLES.items(): assert isinstance(style.position, PositionConfig) assert style.position.vertical in ["top", "center", "bottom", "lower_third"] assert style.position.horizontal in ["left", "center", "right"] def test_all_styles_have_animation(self): """Test all styles have animation configuration.""" for _style_name, style in CAPTION_STYLES.items(): assert isinstance(style.animation, AnimationConfig) assert style.animation.enter assert style.animation.exit assert style.animation.enter_duration > 0 assert style.animation.exit_duration > 0 def test_all_styles_have_platform_optimization(self): """Test all styles specify platform optimization.""" for style_name, style in CAPTION_STYLES.items(): assert len(style.platform_optimized) > 0, ( f"Style '{style_name}' has no platform optimization" ) class TestCaptionStyleDifferences: """Test that caption styles have meaningful differences.""" def test_burst_vs_precise_display_mode(self): """Test burst and precise have different display modes.""" burst = CAPTION_STYLES["burst"] precise = CAPTION_STYLES["precise"] assert burst.display_mode == "word_by_word" assert precise.display_mode == "phrase_by_phrase" def test_burst_has_uppercase_text(self): """Test burst style uses uppercase text.""" burst = CAPTION_STYLES["burst"] assert burst.typography.text_transform == "uppercase" def test_neon_has_glow_effect(self): """Test neon style has glow effect in shadow.""" neon = CAPTION_STYLES["neon"] assert "0 0" in neon.colors.shadow # Glow effect signature def test_classic_has_no_background(self): """Test classic style has no background.""" classic = CAPTION_STYLES["classic"] assert classic.background.enabled is False def test_minimal_vs_headline_font_sizes(self): """Test minimal and headline have different font sizes.""" minimal = CAPTION_STYLES["minimal"] headline = CAPTION_STYLES["headline"] assert headline.typography.font_size > minimal.typography.font_size def test_burst_optimized_for_shorts(self): """Test burst is optimized for short-form platforms.""" burst = CAPTION_STYLES["burst"] short_platforms = ["tiktok", "youtube_shorts", "instagram_reel"] assert any(platform in burst.platform_optimized for platform in short_platforms) def test_precise_optimized_for_long_form(self): """Test precise is optimized for long-form platforms.""" precise = CAPTION_STYLES["precise"] assert "youtube_long_form" in precise.platform_optimized class TestUtilityFunctions: """Test all utility functions.""" def test_get_caption_style(self): """Test get_caption_style utility function.""" style = get_caption_style("burst") assert isinstance(style, CaptionStyle) assert style.name == "burst" def test_get_caption_style_fallback(self): """Test get_caption_style falls back to 'minimal' for unknown styles.""" style = get_caption_style("unknown_style") assert style.name == "minimal" def test_get_style_for_platform_tiktok(self): """Test get_style_for_platform returns correct style for TikTok.""" style_name = get_style_for_platform("tiktok") assert style_name == "burst" def test_get_style_for_platform_youtube(self): """Test get_style_for_platform returns correct style for YouTube.""" style_name = get_style_for_platform("youtube_long_form") assert style_name == "precise" def test_get_style_for_platform_linkedin(self): """Test get_style_for_platform returns correct style for LinkedIn.""" style_name = get_style_for_platform("linkedin") assert style_name == "headline" def test_get_style_for_platform_fallback(self): """Test get_style_for_platform falls back to 'minimal' for unknown platforms.""" style_name = get_style_for_platform("unknown_platform") assert style_name == "minimal" def test_list_caption_styles(self): """Test list_caption_styles returns all style info.""" styles = list_caption_styles() assert isinstance(styles, list) assert len(styles) == 6 # Check structure for style in styles: assert "name" in style assert "display_name" in style assert "description" in style assert "display_mode" in style assert "recommended_tempo" in style class TestCaptionStyleConsistency: """Test consistency across caption styles.""" def test_all_font_weights_reasonable(self): """Test all font weights are reasonable.""" for style_name, style in CAPTION_STYLES.items(): assert 100 <= style.typography.font_weight <= 900, ( f"Style '{style_name}' has unreasonable font weight" ) def test_all_font_sizes_reasonable(self): """Test all font sizes are reasonable.""" for style_name, style in CAPTION_STYLES.items(): assert 24 <= style.typography.font_size <= 100, ( f"Style '{style_name}' has unreasonable font size" ) def test_all_animation_durations_reasonable(self): """Test all animation durations are reasonable.""" for style_name, style in CAPTION_STYLES.items(): assert 0.05 <= style.animation.enter_duration <= 2.0, ( f"Style '{style_name}' has unreasonable enter duration" ) assert 0.05 <= style.animation.exit_duration <= 2.0, ( f"Style '{style_name}' has unreasonable exit duration" ) def test_all_gap_durations_reasonable(self): """Test all gap durations are reasonable.""" for style_name, style in CAPTION_STYLES.items(): assert 0.0 <= style.gap_duration <= 2.0, ( f"Style '{style_name}' has unreasonable gap duration" ) def test_background_styles_valid(self): """Test all background styles are valid.""" valid_styles = ["pill", "box", "none", "gradient"] for style_name, style in CAPTION_STYLES.items(): assert style.background.style in valid_styles, ( f"Style '{style_name}' has invalid background style" ) def test_stroke_width_matches_stroke_presence(self): """Test stroke_width is 0 when stroke is None.""" for style_name, style in CAPTION_STYLES.items(): if style.colors.stroke is None: assert style.colors.stroke_width == 0, ( f"Style '{style_name}' has stroke_width but no stroke" ) def test_platform_optimizations_are_strings(self): """Test all platform optimizations are valid strings.""" for style_name, style in CAPTION_STYLES.items(): for platform in style.platform_optimized: assert isinstance(platform, str), ( f"Style '{style_name}' has non-string platform optimization" ) assert len(platform) > 0 def test_recommended_tempos_valid(self): """Test all recommended tempos reference valid tempo names.""" valid_tempos = ["sprint", "fast", "medium", "slow", "cinematic"] for style_name, style in CAPTION_STYLES.items(): assert style.recommended_tempo in valid_tempos, ( f"Style '{style_name}' has invalid recommended tempo '{style.recommended_tempo}'" ) def test_faster_tempos_have_shorter_durations(self): """Test styles with faster tempos have shorter caption durations.""" burst = CAPTION_STYLES["burst"] # recommended_tempo: "fast" classic = CAPTION_STYLES["classic"] # recommended_tempo: "cinematic" # Burst should have shorter word duration than classic's sentence duration assert burst.word_duration < classic.sentence_duration

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