package sync import ( "os" "path/filepath" "strings" "testing" ) func TestSyncLyrics(t *testing.T) { // Create temporary test directory tempDir := t.TempDir() // Test cases for different format combinations testCases := []struct { name string sourceContent string sourceExt string targetContent string targetExt string expectedError bool validateOutput func(t *testing.T, filePath string) }{ { name: "LRC to LRC sync", sourceContent: `[ti:Source LRC] [ar:Test Artist] [00:01.00]This is line one. [00:05.00]This is line two. [00:09.50]This is line three. `, sourceExt: "lrc", targetContent: `[ti:Target LRC] [ar:Different Artist] [00:10.00]This is line one with different timing. [00:20.00]This is line two with different timing. [00:30.00]This is line three with different timing. `, targetExt: "lrc", expectedError: false, validateOutput: func(t *testing.T, filePath string) { content, err := os.ReadFile(filePath) if err != nil { t.Fatalf("Failed to read output file: %v", err) } contentStr := string(content) // Should contain target title but source timings if !strings.Contains(contentStr, "[ti:Target LRC]") { t.Errorf("Output should preserve target title, got: %s", contentStr) } if !strings.Contains(contentStr, "[ar:Different Artist]") { t.Errorf("Output should preserve target artist, got: %s", contentStr) } // Should have source timings if !strings.Contains(contentStr, "[00:01.000]") { t.Errorf("Output should have source timing [00:01.000], got: %s", contentStr) } // Should have target content if !strings.Contains(contentStr, "This is line one with different timing.") { t.Errorf("Output should preserve target content, got: %s", contentStr) } }, }, { name: "SRT to SRT sync", sourceContent: `1 00:00:01,000 --> 00:00:04,000 This is line one. 2 00:00:05,000 --> 00:00:08,000 This is line two. 3 00:00:09,000 --> 00:00:12,000 This is line three. `, sourceExt: "srt", targetContent: `1 00:01:00,000 --> 00:01:03,000 This is target line one. 2 00:01:05,000 --> 00:01:08,000 This is target line two. 3 00:01:10,000 --> 00:01:13,000 This is target line three. `, targetExt: "srt", expectedError: false, validateOutput: func(t *testing.T, filePath string) { content, err := os.ReadFile(filePath) if err != nil { t.Fatalf("Failed to read output file: %v", err) } contentStr := string(content) // Should have source timings but target content if !strings.Contains(contentStr, "00:00:01,000 -->") { t.Errorf("Output should have source timing 00:00:01,000, got: %s", contentStr) } // Check target content is preserved if !strings.Contains(contentStr, "This is target line one.") { t.Errorf("Output should preserve target content, got: %s", contentStr) } // Check identifiers are sequential if !strings.Contains(contentStr, "1\n00:00:01,000") { t.Errorf("Output should have sequential identifiers starting with 1, got: %s", contentStr) } }, }, { name: "VTT to VTT sync", sourceContent: `WEBVTT 1 00:00:01.000 --> 00:00:04.000 This is line one. 2 00:00:05.000 --> 00:00:08.000 This is line two. 3 00:00:09.000 --> 00:00:12.000 This is line three. `, sourceExt: "vtt", targetContent: `WEBVTT - Target Title 1 00:01:00.000 --> 00:01:03.000 align:start position:10% This is target line one. 2 00:01:05.000 --> 00:01:08.000 align:middle This is target line two. 3 00:01:10.000 --> 00:01:13.000 This is target line three. `, targetExt: "vtt", expectedError: false, validateOutput: func(t *testing.T, filePath string) { content, err := os.ReadFile(filePath) if err != nil { t.Fatalf("Failed to read output file: %v", err) } contentStr := string(content) // Should preserve VTT title if !strings.Contains(contentStr, "WEBVTT - Target Title") { t.Errorf("Output should preserve target title, got: %s", contentStr) } // Should have source timings but target content and settings if !strings.Contains(contentStr, "00:00:01.000 -->") { t.Errorf("Output should have source timing 00:00:01.000, got: %s", contentStr) } // Should preserve styling cue settings if !strings.Contains(contentStr, "align:start position:10%") { t.Errorf("Output should preserve cue settings, got: %s", contentStr) } // Check target content is preserved if !strings.Contains(contentStr, "This is target line one.") { t.Errorf("Output should preserve target content, got: %s", contentStr) } }, }, { name: "ASS to ASS sync", sourceContent: `[Script Info] ScriptType: v4.00+ PlayResX: 640 PlayResY: 480 Timer: 100.0000 Title: Source ASS File [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,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1 [Events] Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text Dialogue: 0,0:00:01.00,0:00:04.00,Default,,0,0,0,,Source line one. Dialogue: 0,0:00:05.00,0:00:08.00,Default,,0,0,0,,Source line two. Dialogue: 0,0:00:09.00,0:00:12.00,Default,,0,0,0,,Source line three. `, sourceExt: "ass", targetContent: `[Script Info] ScriptType: v4.00+ PlayResX: 640 PlayResY: 480 Timer: 100.0000 Title: Target ASS File [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,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1 Style: Alternate,Arial,20,&H0000FFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1 [Events] Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text Dialogue: 0,0:01:00.00,0:01:03.00,Default,,0,0,0,,Target line one. Dialogue: 0,0:01:05.00,0:01:08.00,Alternate,,0,0,0,,Target line two. Dialogue: 0,0:01:10.00,0:01:13.00,Default,,0,0,0,,Target line three. `, targetExt: "ass", expectedError: false, validateOutput: func(t *testing.T, filePath string) { content, err := os.ReadFile(filePath) if err != nil { t.Fatalf("Failed to read output file: %v", err) } contentStr := string(content) // Should have source timings but target content if !strings.Contains(contentStr, "0:00:01.00") { t.Errorf("Output should have source timing 0:00:01.00, got: %s", contentStr) } // Check target content is preserved if !strings.Contains(contentStr, "Target line one.") { t.Errorf("Output should preserve target content, got: %s", contentStr) } // Check target styles are preserved if !strings.Contains(contentStr, "Style: Alternate") { t.Errorf("Output should preserve target styles, got: %s", contentStr) } // Check target title is preserved if !strings.Contains(contentStr, "Title: Target ASS File") { t.Errorf("Output should preserve target title, got: %s", contentStr) } }, }, { name: "Unsupported format combination", sourceContent: `[00:01.00]This is line one.`, sourceExt: "lrc", targetContent: `1\n00:00:01,000 --> 00:00:04,000\nThis is line one.`, targetExt: "srt", expectedError: true, validateOutput: func(t *testing.T, filePath string) { // Not needed for error case }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { sourceFile := filepath.Join(tempDir, "source."+tc.sourceExt) targetFile := filepath.Join(tempDir, "target."+tc.targetExt) // Write test files if err := os.WriteFile(sourceFile, []byte(tc.sourceContent), 0644); err != nil { t.Fatalf("Failed to write source file: %v", err) } if err := os.WriteFile(targetFile, []byte(tc.targetContent), 0644); err != nil { t.Fatalf("Failed to write target file: %v", err) } // Run SyncLyrics err := SyncLyrics(sourceFile, targetFile) // Check error status if tc.expectedError && err == nil { t.Errorf("Expected error but got nil") } else if !tc.expectedError && err != nil { t.Errorf("Unexpected error: %v", err) } // If no error is expected, validate the output if !tc.expectedError && err == nil { tc.validateOutput(t, targetFile) } }) } }