package sync import ( "testing" "sub-cli/internal/model" ) func TestSyncLRCTimeline(t *testing.T) { testCases := []struct { name string source model.Lyrics target model.Lyrics verify func(t *testing.T, result model.Lyrics) }{ { name: "Equal content length", source: model.Lyrics{ Metadata: map[string]string{ "ti": "Source LRC", "ar": "Test Artist", }, Timeline: []model.Timestamp{ {Minutes: 0, Seconds: 1, Milliseconds: 0}, {Minutes: 0, Seconds: 5, Milliseconds: 0}, {Minutes: 0, Seconds: 9, Milliseconds: 500}, }, Content: []string{ "This is line one.", "This is line two.", "This is line three.", }, }, target: model.Lyrics{ Metadata: map[string]string{ "ti": "Target LRC", "ar": "Different Artist", }, Timeline: []model.Timestamp{ {Minutes: 0, Seconds: 10, Milliseconds: 0}, {Minutes: 0, Seconds: 20, Milliseconds: 0}, {Minutes: 0, Seconds: 30, Milliseconds: 0}, }, Content: []string{ "This is line one with different timing.", "This is line two with different timing.", "This is line three with different timing.", }, }, verify: func(t *testing.T, result model.Lyrics) { if len(result.Timeline) != 3 { t.Errorf("Expected 3 timeline entries, got %d", len(result.Timeline)) return } // Verify that source timings are applied if result.Timeline[0].Seconds != 1 || result.Timeline[0].Milliseconds != 0 { t.Errorf("First timeline entry should have source timing, got: %+v", result.Timeline[0]) } if result.Timeline[1].Seconds != 5 || result.Timeline[1].Milliseconds != 0 { t.Errorf("Second timeline entry should have source timing, got: %+v", result.Timeline[1]) } if result.Timeline[2].Seconds != 9 || result.Timeline[2].Milliseconds != 500 { t.Errorf("Third timeline entry should have source timing, got: %+v", result.Timeline[2]) } // Verify that target content is preserved if result.Content[0] != "This is line one with different timing." { t.Errorf("Content should be preserved, got: %s", result.Content[0]) } // Verify that target metadata is preserved if result.Metadata["ti"] != "Target LRC" || result.Metadata["ar"] != "Different Artist" { t.Errorf("Metadata should be preserved, got: %+v", result.Metadata) } }, }, { name: "More target content than source timeline", source: model.Lyrics{ Timeline: []model.Timestamp{ {Minutes: 0, Seconds: 1, Milliseconds: 0}, {Minutes: 0, Seconds: 5, Milliseconds: 0}, }, Content: []string{ "This is line one.", "This is line two.", }, }, target: model.Lyrics{ Metadata: map[string]string{ "ti": "Target LRC", }, Timeline: []model.Timestamp{ {Minutes: 0, Seconds: 10, Milliseconds: 0}, {Minutes: 0, Seconds: 20, Milliseconds: 0}, {Minutes: 0, Seconds: 30, Milliseconds: 0}, }, Content: []string{ "This is line one with different timing.", "This is line two with different timing.", "This is line three with different timing.", }, }, verify: func(t *testing.T, result model.Lyrics) { if len(result.Timeline) != 3 { t.Errorf("Expected 3 timeline entries, got %d", len(result.Timeline)) return } // Verify that source timings are scaled if result.Timeline[0].Seconds != 1 { t.Errorf("First timeline entry should have first source timing, got: %+v", result.Timeline[0]) } if result.Timeline[2].Seconds != 5 { t.Errorf("Last timeline entry should have last source timing, got: %+v", result.Timeline[2]) } // Verify that target content is preserved if result.Content[2] != "This is line three with different timing." { t.Errorf("Content should be preserved, got: %s", result.Content[2]) } // Verify that target metadata is preserved if result.Metadata["ti"] != "Target LRC" { t.Errorf("Metadata should be preserved, got: %+v", result.Metadata) } }, }, { name: "More source timeline than target content", source: model.Lyrics{ Timeline: []model.Timestamp{ {Minutes: 0, Seconds: 1, Milliseconds: 0}, {Minutes: 0, Seconds: 3, Milliseconds: 0}, {Minutes: 0, Seconds: 5, Milliseconds: 0}, {Minutes: 0, Seconds: 7, Milliseconds: 0}, }, Content: []string{ "Source line one.", "Source line two.", "Source line three.", "Source line four.", }, }, target: model.Lyrics{ Metadata: map[string]string{ "ti": "Target LRC", }, Timeline: []model.Timestamp{ {Minutes: 0, Seconds: 10, Milliseconds: 0}, {Minutes: 0, Seconds: 20, Milliseconds: 0}, }, Content: []string{ "Target line one.", "Target line two.", }, }, verify: func(t *testing.T, result model.Lyrics) { if len(result.Timeline) != 2 { t.Errorf("Expected 2 timeline entries, got %d", len(result.Timeline)) return } // Verify that source timings are scaled if result.Timeline[0].Seconds != 1 { t.Errorf("First timeline entry should have first source timing, got: %+v", result.Timeline[0]) } if result.Timeline[1].Seconds != 7 { t.Errorf("Last timeline entry should have last source timing, got: %+v", result.Timeline[1]) } // Verify that target content is preserved if result.Content[0] != "Target line one." || result.Content[1] != "Target line two." { t.Errorf("Content should be preserved, got: %+v", result.Content) } }, }, { name: "Empty target content", source: model.Lyrics{ Timeline: []model.Timestamp{ {Minutes: 0, Seconds: 1, Milliseconds: 0}, }, Content: []string{ "Source line one.", }, }, target: model.Lyrics{ Metadata: map[string]string{ "ti": "Empty Target", }, Timeline: []model.Timestamp{}, Content: []string{}, }, verify: func(t *testing.T, result model.Lyrics) { if len(result.Timeline) != 0 { t.Errorf("Expected 0 timeline entries, got %d", len(result.Timeline)) } if len(result.Content) != 0 { t.Errorf("Expected 0 content entries, got %d", len(result.Content)) } // Verify that target metadata is preserved if result.Metadata["ti"] != "Empty Target" { t.Errorf("Metadata should be preserved, got: %+v", result.Metadata) } }, }, { name: "Empty source timeline", source: model.Lyrics{ Timeline: []model.Timestamp{}, Content: []string{}, }, target: model.Lyrics{ Metadata: map[string]string{ "ti": "Target with content", }, Timeline: []model.Timestamp{ {Minutes: 0, Seconds: 10, Milliseconds: 0}, }, Content: []string{ "Target line one.", }, }, verify: func(t *testing.T, result model.Lyrics) { if len(result.Timeline) != 1 { t.Errorf("Expected 1 timeline entry, got %d", len(result.Timeline)) return } // Verify that target timing is preserved when source is empty if result.Timeline[0].Seconds != 10 { t.Errorf("Timeline should match target when source is empty, got: %+v", result.Timeline[0]) } // Verify that target content is preserved if result.Content[0] != "Target line one." { t.Errorf("Content should be preserved, got: %s", result.Content[0]) } // Verify that target metadata is preserved if result.Metadata["ti"] != "Target with content" { t.Errorf("Metadata should be preserved, got: %+v", result.Metadata) } }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := syncLRCTimeline(tc.source, tc.target) if tc.verify != nil { tc.verify(t, result) } }) } }