ass-core 0.1.1

High-performance ASS subtitle format parser and analyzer
Documentation
//! Edge case and error handling tests for the styles parser.
//!
//! This module contains comprehensive tests targeting previously untested code paths
//! in the styles parser, focusing on format handling, field validation, and error recovery.

use ass_core::{parser::IssueSeverity, Script};

#[cfg(test)]
mod tests {
    use super::*;

    /// Test Style lines without Format line to cover default format fallback (L86-L113)
    #[test]
    fn test_styles_without_format_line() {
        let script_no_format = r"
[V4+ Styles]
Style: Default,Arial,20,&Hffffff,&H0,&H0,&H0,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
Style: Large,Arial,40,&Hff0000,&H0,&H0,&H0,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script = Script::parse(script_no_format).expect("Script parsing should work");

        // Should parse successfully even without explicit Format line
        assert!(!script.sections().is_empty());

        // May have warnings about missing format but should still process styles
        let _has_format_warnings = script.issues().iter().any(|issue| {
            issue.message.to_lowercase().contains("format")
                || issue.message.to_lowercase().contains("field")
        });
    }

    /// Test V4 Styles section (older format)
    #[test]
    fn test_v4_styles_section() {
        let script_v4 = r"
[V4 Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding, RelativeToVideo
Style: Default,Arial,20,16777215,255,0,0,0,0,1,2,0,2,10,10,10,0,1,0

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script = Script::parse(script_v4).expect("Script parsing should work");

        // Should parse V4 styles successfully
        assert!(!script.sections().is_empty());
    }

    /// Test Style line with wrong number of fields (L130-L141)
    #[test]
    fn test_style_field_count_mismatch() {
        // Test with fewer fields than Format specifies
        let script_too_few = r"
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,20,&Hffffff
Style: Complete,Arial,20,&Hffffff,&H0,&H0,&H0,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script = Script::parse(script_too_few).expect("Script parsing should work");

        // Should have warnings about field count mismatch
        let _has_mismatch_warnings = script.issues().iter().any(|issue| {
            matches!(issue.severity, IssueSeverity::Warning)
                && (issue.message.contains("field") || issue.message.contains("count"))
        });

        // Test with more fields than Format specifies
        let script_too_many = r"
[V4+ Styles]
Format: Name, Fontname, Fontsize
Style: Default,Arial,20,&Hffffff,&H0,&H0,&H0,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1,extra,fields

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script_many = Script::parse(script_too_many).expect("Script parsing should work");

        // Should handle extra fields gracefully
        assert!(!script_many.sections().is_empty());
    }

    /// Test missing fields in Format line (L143-L149)
    #[test]
    fn test_missing_format_fields() {
        let script_missing_fields = r"
[V4+ Styles]
Format: Name, Fontname
Style: Default,Arial,20,&Hffffff,&H0,&H0,&H0,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script = Script::parse(script_missing_fields).expect("Script parsing should work");

        // Should handle missing format fields gracefully
        assert!(!script.sections().is_empty());

        // Parser should handle when trying to access fields not in Format
        let _has_missing_field_issues = script
            .issues()
            .iter()
            .any(|issue| issue.message.contains("field") || issue.message.contains("missing"));
    }

    /// Test files ending abruptly during styles parsing (L184-L191, L204-L218)
    #[test]
    fn test_abrupt_ending_in_styles() {
        // Test ending in middle of style line
        let truncated_style = r"
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour
Style: Default,Arial,20";

        let script = Script::parse(truncated_style).expect("Script parsing should work");

        // Should handle truncated file gracefully
        assert!(!script.sections().is_empty() || !script.issues().is_empty());

        // Test ending in middle of comment
        let truncated_comment = r"
[V4+ Styles]
Format: Name, Fontname, Fontsize
; This is a comment that gets cut off in the mid";

        let script_comment = Script::parse(truncated_comment).expect("Script parsing should work");

        // Should handle truncated comments
        let _has_sections_or_issues =
            !script_comment.sections().is_empty() || !script_comment.issues().is_empty();

        // Test ending with whitespace
        let ending_whitespace = r"
[V4+ Styles]
Format: Name, Fontname, Fontsize
Style: Default,Arial,20

   ";

        let script_ws = Script::parse(ending_whitespace).expect("Script parsing should work");

        // Should handle trailing whitespace
        assert!(!script_ws.sections().is_empty());
    }

    /// Test malformed Format lines
    #[test]
    fn test_malformed_format_lines() {
        // Test Format line with no fields
        let empty_format = r"
[V4+ Styles]
Format:
Style: Default,Arial,20

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script = Script::parse(empty_format).expect("Script parsing should work");

        // Should handle empty format gracefully
        assert!(!script.sections().is_empty());

        // Test Format line with only spaces
        let spaces_format = r"
[V4+ Styles]
Format:
Style: Default,Arial,20

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script_spaces = Script::parse(spaces_format).expect("Script parsing should work");
        assert!(!script_spaces.sections().is_empty());
    }

    /// Test styles with special characters and edge cases
    #[test]
    fn test_styles_special_characters() {
        let special_chars = r"
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour
Style: Default,Arial,20,&Hffffff
Style: Special!@#$%,Arial,20,&H00ff00
Style: Unicode测试,Arial,20,&Hff0000
Style: Empty,,20,&H0000ff
Style: Comma\,Name,Arial,20,&Hffff00

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script = Script::parse(special_chars).expect("Script parsing should work");

        // Should handle special characters in style names
        assert!(!script.sections().is_empty());

        // Should not have critical errors
        let _has_critical_errors = script
            .issues()
            .iter()
            .any(|issue| matches!(issue.severity, IssueSeverity::Error));
    }

    /// Test multiple Format lines in same section
    #[test]
    fn test_multiple_format_lines() {
        let multiple_formats = r"
[V4+ Styles]
Format: Name, Fontname, Fontsize
Format: Name, Fontname, Fontsize, PrimaryColour
Style: Default,Arial,20,&Hffffff

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script = Script::parse(multiple_formats).expect("Script parsing should work");

        // Should handle multiple format lines (probably uses the last one)
        assert!(!script.sections().is_empty());
    }

    /// Test empty styles section
    #[test]
    fn test_empty_styles_section() {
        let empty_section = r"
[V4+ Styles]

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script = Script::parse(empty_section).expect("Script parsing should work");

        // Should handle empty styles section
        assert!(!script.sections().is_empty());
    }

    /// Test styles section with only comments
    #[test]
    fn test_styles_only_comments() {
        let only_comments = r"
[V4+ Styles]
; This is a comment
; Another comment
!: This is also a comment

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script = Script::parse(only_comments).expect("Script parsing should work");

        // Should handle section with only comments
        assert!(!script.sections().is_empty());
    }

    /// Test style lines with unusual spacing and formatting
    #[test]
    fn test_style_spacing_variations() {
        let spacing_variations = r"
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour
Style:Default,Arial,20,&Hffffff
Style:    Spaced   ,   Arial   ,   20   ,   &H00ff00
Style: 	Tabbed	,	Arial	,	20	,	&Hff0000
Style: Mixed   ,	Arial,  20,&H0000ff

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script = Script::parse(spacing_variations).expect("Script parsing should work");

        // Should handle various spacing patterns
        assert!(!script.sections().is_empty());
    }

    /// Test boundary conditions for field parsing
    #[test]
    fn test_field_parsing_boundaries() {
        let boundary_cases = r"
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Minimum,,,,,,,,,,,,,,,,,,,,,
Style: Maximum,Arial,999,&HFFFFFFFF,&HFFFFFFFF,&HFFFFFFFF,&HFFFFFFFF,1,1,1,1,1000,1000,100,360,4,100,100,11,9999,9999,9999,255
Style: Negative,Arial,-1,&H0,&H0,&H0,&H0,-1,-1,-1,-1,-100,-100,-100,-360,-1,-100,-100,-1,-1,-1,-1,-1

[Events\]
Format: Start, End, Style, Text
Dialogue: 0:00:00.00,0:00:05.00,Default,Test
";

        let script = Script::parse(boundary_cases).expect("Script parsing should work");

        // Should handle boundary values (minimum, maximum, negative)
        assert!(!script.sections().is_empty());

        // Should not crash on extreme values
        let _has_sections = !script.sections().is_empty();
    }
}