package sync import ( "testing" "sub-cli/internal/model" ) func TestSyncVTTTimeline(t *testing.T) { testCases := []struct { name string source model.Subtitle target model.Subtitle verify func(t *testing.T, result model.Subtitle) }{ { name: "Equal entry counts", source: model.Subtitle{ Format: "vtt", Title: "Source VTT", Entries: []model.SubtitleEntry{ { Index: 1, StartTime: model.Timestamp{Seconds: 1}, EndTime: model.Timestamp{Seconds: 4}, Text: "Source line one.", }, { Index: 2, StartTime: model.Timestamp{Seconds: 5}, EndTime: model.Timestamp{Seconds: 8}, Text: "Source line two.", }, { Index: 3, StartTime: model.Timestamp{Seconds: 9}, EndTime: model.Timestamp{Seconds: 12}, Text: "Source line three.", }, }, }, target: model.Subtitle{ Format: "vtt", Title: "Target VTT", Styles: map[string]string{ "style1": ".style1 { color: red; }", }, Entries: []model.SubtitleEntry{ { Index: 1, StartTime: model.Timestamp{Minutes: 1, Seconds: 0}, EndTime: model.Timestamp{Minutes: 1, Seconds: 3}, Text: "Target line one.", Styles: map[string]string{ "align": "start", "position": "10%", }, }, { Index: 2, StartTime: model.Timestamp{Minutes: 1, Seconds: 5}, EndTime: model.Timestamp{Minutes: 1, Seconds: 8}, Text: "Target line two.", Styles: map[string]string{ "align": "middle", }, }, { Index: 3, StartTime: model.Timestamp{Minutes: 1, Seconds: 10}, EndTime: model.Timestamp{Minutes: 1, Seconds: 13}, Text: "Target line three.", }, }, }, verify: func(t *testing.T, result model.Subtitle) { if len(result.Entries) != 3 { t.Errorf("Expected 3 entries, got %d", len(result.Entries)) return } // Check that source timings are applied to target entries if result.Entries[0].StartTime.Seconds != 1 || result.Entries[0].EndTime.Seconds != 4 { t.Errorf("First entry timing mismatch: got %+v", result.Entries[0]) } if result.Entries[1].StartTime.Seconds != 5 || result.Entries[1].EndTime.Seconds != 8 { t.Errorf("Second entry timing mismatch: got %+v", result.Entries[1]) } if result.Entries[2].StartTime.Seconds != 9 || result.Entries[2].EndTime.Seconds != 12 { t.Errorf("Third entry timing mismatch: got %+v", result.Entries[2]) } // Check that target content is preserved if result.Entries[0].Text != "Target line one." { t.Errorf("Content should be preserved, got: %s", result.Entries[0].Text) } // Check that styles are preserved if result.Entries[0].Styles["align"] != "start" || result.Entries[0].Styles["position"] != "10%" { t.Errorf("Styles should be preserved, got: %+v", result.Entries[0].Styles) } // Check that global styles are preserved if result.Styles["style1"] != ".style1 { color: red; }" { t.Errorf("Global styles should be preserved, got: %+v", result.Styles) } // Check that numbering is correct if result.Entries[0].Index != 1 || result.Entries[1].Index != 2 || result.Entries[2].Index != 3 { t.Errorf("Entry indices should be sequential: %+v", result.Entries) } }, }, { name: "More target entries than source", source: model.Subtitle{ Format: "vtt", Entries: []model.SubtitleEntry{ { Index: 1, StartTime: model.Timestamp{Seconds: 1}, EndTime: model.Timestamp{Seconds: 4}, Text: "Source line one.", }, { Index: 2, StartTime: model.Timestamp{Seconds: 5}, EndTime: model.Timestamp{Seconds: 8}, Text: "Source line two.", }, }, }, target: model.Subtitle{ Format: "vtt", Title: "Target VTT", Entries: []model.SubtitleEntry{ { Index: 1, StartTime: model.Timestamp{Minutes: 1, Seconds: 0}, EndTime: model.Timestamp{Minutes: 1, Seconds: 3}, Text: "Target line one.", }, { Index: 2, StartTime: model.Timestamp{Minutes: 1, Seconds: 5}, EndTime: model.Timestamp{Minutes: 1, Seconds: 8}, Text: "Target line two.", }, { Index: 3, StartTime: model.Timestamp{Minutes: 1, Seconds: 10}, EndTime: model.Timestamp{Minutes: 1, Seconds: 13}, Text: "Target line three.", }, }, }, verify: func(t *testing.T, result model.Subtitle) { if len(result.Entries) != 3 { t.Errorf("Expected 3 entries, got %d", len(result.Entries)) return } // First entry should use first source timing if result.Entries[0].StartTime.Seconds != 1 { t.Errorf("First entry should have first source timing, got: %+v", result.Entries[0].StartTime) } // Last entry should use last source timing if result.Entries[2].StartTime.Seconds != 5 { t.Errorf("Last entry should have last source timing, got: %+v", result.Entries[2].StartTime) } // Check that target content is preserved if result.Entries[2].Text != "Target line three." { t.Errorf("Content should be preserved, got: %s", result.Entries[2].Text) } // Check that title is preserved if result.Title != "Target VTT" { t.Errorf("Title should be preserved, got: %s", result.Title) } }, }, { name: "More source entries than target", source: model.Subtitle{ Format: "vtt", Entries: []model.SubtitleEntry{ { Index: 1, StartTime: model.Timestamp{Seconds: 1}, EndTime: model.Timestamp{Seconds: 3}, Text: "Source line one.", }, { Index: 2, StartTime: model.Timestamp{Seconds: 4}, EndTime: model.Timestamp{Seconds: 6}, Text: "Source line two.", }, { Index: 3, StartTime: model.Timestamp{Seconds: 7}, EndTime: model.Timestamp{Seconds: 9}, Text: "Source line three.", }, { Index: 4, StartTime: model.Timestamp{Seconds: 10}, EndTime: model.Timestamp{Seconds: 12}, Text: "Source line four.", }, }, }, target: model.Subtitle{ Format: "vtt", Metadata: map[string]string{ "Region": "metadata region", }, Entries: []model.SubtitleEntry{ { Index: 1, StartTime: model.Timestamp{Minutes: 1, Seconds: 0}, EndTime: model.Timestamp{Minutes: 1, Seconds: 3}, Text: "Target line one.", }, { Index: 2, StartTime: model.Timestamp{Minutes: 1, Seconds: 5}, EndTime: model.Timestamp{Minutes: 1, Seconds: 8}, Text: "Target line two.", }, }, }, verify: func(t *testing.T, result model.Subtitle) { if len(result.Entries) != 2 { t.Errorf("Expected 2 entries, got %d", len(result.Entries)) return } // First entry should have first source timing if result.Entries[0].StartTime.Seconds != 1 { t.Errorf("First entry should have first source timing, got: %+v", result.Entries[0].StartTime) } // Last entry should have last source timing if result.Entries[1].StartTime.Seconds != 10 { t.Errorf("Last entry should have last source timing, got: %+v", result.Entries[1].StartTime) } // Check that metadata is preserved if result.Metadata["Region"] != "metadata region" { t.Errorf("Metadata should be preserved, got: %+v", result.Metadata) } // Check that target content is preserved if result.Entries[0].Text != "Target line one." || result.Entries[1].Text != "Target line two." { t.Errorf("Content should be preserved, got: %+v", result.Entries) } }, }, { name: "Empty target entries", source: model.Subtitle{ Format: "vtt", Entries: []model.SubtitleEntry{ { Index: 1, StartTime: model.Timestamp{Seconds: 1}, EndTime: model.Timestamp{Seconds: 4}, Text: "Source line one.", }, }, }, target: model.Subtitle{ Format: "vtt", Title: "Empty Target", }, verify: func(t *testing.T, result model.Subtitle) { if len(result.Entries) != 0 { t.Errorf("Expected 0 entries, got %d", len(result.Entries)) } // Title should be preserved if result.Title != "Empty Target" { t.Errorf("Title should be preserved, got: %s", result.Title) } }, }, { name: "Empty source entries", source: model.Subtitle{ Format: "vtt", }, target: model.Subtitle{ Format: "vtt", Title: "Target with content", Entries: []model.SubtitleEntry{ { Index: 1, StartTime: model.Timestamp{Seconds: 10}, EndTime: model.Timestamp{Seconds: 15}, Text: "Target line one.", }, }, }, verify: func(t *testing.T, result model.Subtitle) { if len(result.Entries) != 1 { t.Errorf("Expected 1 entry, got %d", len(result.Entries)) return } // Timing should be preserved since source is empty if result.Entries[0].StartTime.Seconds != 10 || result.Entries[0].EndTime.Seconds != 15 { t.Errorf("Timing should match target when source is empty, got: %+v", result.Entries[0]) } // Content should be preserved if result.Entries[0].Text != "Target line one." { t.Errorf("Content should be preserved, got: %s", result.Entries[0].Text) } // Title should be preserved if result.Title != "Target with content" { t.Errorf("Title should be preserved, got: %s", result.Title) } }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := syncVTTTimeline(tc.source, tc.target) if tc.verify != nil { tc.verify(t, result) } }) } }