sub-cli/internal/sync/sync_test.go
2025-04-23 19:22:41 +08:00

297 lines
8.5 KiB
Go

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)
}
})
}
}