chore: seperate large files

This commit is contained in:
CDN 2025-04-23 19:22:41 +08:00
parent ebbf516689
commit 76e1298ded
Signed by: CDN
GPG key ID: 0C656827F9F80080
44 changed files with 5745 additions and 4173 deletions

View file

@ -0,0 +1,181 @@
package lrc
import (
"os"
"path/filepath"
"strings"
"testing"
"sub-cli/internal/model"
)
func TestConvertToSubtitle(t *testing.T) {
// Create a temporary test file
content := `[ti:Test LRC File]
[ar:Test Artist]
[00:01.00]This is the first line.
[00:05.00]This is the second line.
`
tempDir := t.TempDir()
testFile := filepath.Join(tempDir, "test.lrc")
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
// Convert to subtitle
subtitle, err := ConvertToSubtitle(testFile)
if err != nil {
t.Fatalf("ConvertToSubtitle failed: %v", err)
}
// Check result
if subtitle.Format != "lrc" {
t.Errorf("Expected format 'lrc', got '%s'", subtitle.Format)
}
if len(subtitle.Entries) != 2 {
t.Errorf("Expected 2 entries, got %d", len(subtitle.Entries))
}
// Check first entry
if subtitle.Entries[0].StartTime.Hours != 0 || subtitle.Entries[0].StartTime.Minutes != 0 ||
subtitle.Entries[0].StartTime.Seconds != 1 || subtitle.Entries[0].StartTime.Milliseconds != 0 {
t.Errorf("First entry start time: expected 00:01.00, got %+v", subtitle.Entries[0].StartTime)
}
if subtitle.Entries[0].Text != "This is the first line." {
t.Errorf("First entry text: expected 'This is the first line.', got '%s'", subtitle.Entries[0].Text)
}
// Check metadata conversion
if subtitle.Title != "Test LRC File" {
t.Errorf("Expected title 'Test LRC File', got '%s'", subtitle.Title)
}
if subtitle.Metadata["ar"] != "Test Artist" {
t.Errorf("Expected artist metadata 'Test Artist', got '%s'", subtitle.Metadata["ar"])
}
}
func TestConvertFromSubtitle(t *testing.T) {
// Create a subtitle
subtitle := model.NewSubtitle()
subtitle.Format = "lrc"
subtitle.Title = "Test LRC File"
subtitle.Metadata["ar"] = "Test Artist"
entry1 := model.NewSubtitleEntry()
entry1.Index = 1
entry1.StartTime = model.Timestamp{Hours: 0, Minutes: 0, Seconds: 1, Milliseconds: 0}
entry1.EndTime = model.Timestamp{Hours: 0, Minutes: 0, Seconds: 4, Milliseconds: 0}
entry1.Text = "This is the first line."
entry2 := model.NewSubtitleEntry()
entry2.Index = 2
entry2.StartTime = model.Timestamp{Hours: 0, Minutes: 0, Seconds: 5, Milliseconds: 0}
entry2.EndTime = model.Timestamp{Hours: 0, Minutes: 0, Seconds: 8, Milliseconds: 0}
entry2.Text = "This is the second line."
subtitle.Entries = append(subtitle.Entries, entry1, entry2)
// Convert to LRC
tempDir := t.TempDir()
outputFile := filepath.Join(tempDir, "output.lrc")
err := ConvertFromSubtitle(subtitle, outputFile)
if err != nil {
t.Fatalf("ConvertFromSubtitle failed: %v", err)
}
// Verify by reading the file directly
content, err := os.ReadFile(outputFile)
if err != nil {
t.Fatalf("Failed to read output file: %v", err)
}
// Check content
contentStr := string(content)
// Check metadata
if !strings.Contains(contentStr, "[ti:Test LRC File]") {
t.Errorf("Expected title metadata in output, not found")
}
if !strings.Contains(contentStr, "[ar:Test Artist]") {
t.Errorf("Expected artist metadata in output, not found")
}
// Check timeline entries
if !strings.Contains(contentStr, "[00:01.000]This is the first line.") {
t.Errorf("Expected first timeline entry in output, not found")
}
if !strings.Contains(contentStr, "[00:05.000]This is the second line.") {
t.Errorf("Expected second timeline entry in output, not found")
}
}
func TestConvertToSubtitle_FileError(t *testing.T) {
// Test with non-existent file
_, err := ConvertToSubtitle("/nonexistent/file.lrc")
if err == nil {
t.Error("Expected error when converting non-existent file, got nil")
}
}
func TestConvertToSubtitle_EdgeCases(t *testing.T) {
// Test with empty lyrics (no content/timeline)
tempDir := t.TempDir()
emptyLyricsFile := filepath.Join(tempDir, "empty_lyrics.lrc")
content := `[ti:Test LRC File]
[ar:Test Artist]
`
if err := os.WriteFile(emptyLyricsFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create empty lyrics test file: %v", err)
}
subtitle, err := ConvertToSubtitle(emptyLyricsFile)
if err != nil {
t.Fatalf("ConvertToSubtitle failed on empty lyrics: %v", err)
}
if len(subtitle.Entries) != 0 {
t.Errorf("Expected 0 entries for empty lyrics, got %d", len(subtitle.Entries))
}
if subtitle.Title != "Test LRC File" {
t.Errorf("Expected title 'Test LRC File', got '%s'", subtitle.Title)
}
// Test with more content than timeline entries
moreContentFile := filepath.Join(tempDir, "more_content.lrc")
content = `[ti:Test LRC File]
[00:01.00]This has a timestamp.
This doesn't have a timestamp but is content.
`
if err := os.WriteFile(moreContentFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create more content test file: %v", err)
}
subtitle, err = ConvertToSubtitle(moreContentFile)
if err != nil {
t.Fatalf("ConvertToSubtitle failed on file with more content than timestamps: %v", err)
}
if len(subtitle.Entries) != 1 {
t.Errorf("Expected 1 entry (only the one with timestamp), got %d", len(subtitle.Entries))
}
}
func TestConvertFromSubtitle_FileError(t *testing.T) {
// Create simple subtitle
subtitle := model.NewSubtitle()
subtitle.Entries = append(subtitle.Entries, model.NewSubtitleEntry())
// Test with invalid path
err := ConvertFromSubtitle(subtitle, "/nonexistent/directory/file.lrc")
if err == nil {
t.Error("Expected error when converting to invalid path, got nil")
}
}

View file

@ -0,0 +1,72 @@
package lrc
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestFormat(t *testing.T) {
// Create a temporary test file with messy formatting
content := `[ti:Test LRC File]
[ar:Test Artist]
[00:01.00]This should be first.
[00:05.00]This is the second line.
[00:09.50]This is the third line.
`
tempDir := t.TempDir()
testFile := filepath.Join(tempDir, "test.lrc")
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
// Format the file
err := Format(testFile)
if err != nil {
t.Fatalf("Format failed: %v", err)
}
// Read the formatted file
formatted, err := os.ReadFile(testFile)
if err != nil {
t.Fatalf("Failed to read formatted file: %v", err)
}
// Check that the file was at least generated successfully
lines := strings.Split(string(formatted), "\n")
if len(lines) < 4 {
t.Fatalf("Expected at least 4 lines, got %d", len(lines))
}
// Check that the metadata was preserved
if !strings.Contains(string(formatted), "[ti:Test LRC File]") {
t.Errorf("Expected title metadata in output, not found")
}
if !strings.Contains(string(formatted), "[ar:Test Artist]") {
t.Errorf("Expected artist metadata in output, not found")
}
// Check that all the content lines are present
if !strings.Contains(string(formatted), "This should be first") {
t.Errorf("Expected 'This should be first' in output, not found")
}
if !strings.Contains(string(formatted), "This is the second line") {
t.Errorf("Expected 'This is the second line' in output, not found")
}
if !strings.Contains(string(formatted), "This is the third line") {
t.Errorf("Expected 'This is the third line' in output, not found")
}
}
func TestFormat_FileError(t *testing.T) {
// Test with non-existent file
err := Format("/nonexistent/file.lrc")
if err == nil {
t.Error("Expected error when formatting non-existent file, got nil")
}
}

View file

@ -0,0 +1,151 @@
package lrc
import (
"os"
"path/filepath"
"strings"
"testing"
"sub-cli/internal/model"
)
func TestGenerate(t *testing.T) {
// Create test lyrics
lyrics := model.Lyrics{
Metadata: map[string]string{
"ti": "Test LRC File",
"ar": "Test Artist",
},
Timeline: []model.Timestamp{
{Hours: 0, Minutes: 0, Seconds: 1, Milliseconds: 0},
{Hours: 0, Minutes: 0, Seconds: 5, Milliseconds: 0},
},
Content: []string{
"This is the first line.",
"This is the second line.",
},
}
// Generate LRC file
tempDir := t.TempDir()
outputFile := filepath.Join(tempDir, "output.lrc")
err := Generate(lyrics, outputFile)
if err != nil {
t.Fatalf("Generate failed: %v", err)
}
// Verify generated content
content, err := os.ReadFile(outputFile)
if err != nil {
t.Fatalf("Failed to read output file: %v", err)
}
// Check content
lines := strings.Split(string(content), "\n")
if len(lines) < 4 {
t.Fatalf("Expected at least 4 lines, got %d", len(lines))
}
hasTitleLine := false
hasFirstTimeline := false
for _, line := range lines {
if line == "[ti:Test LRC File]" {
hasTitleLine = true
}
if line == "[00:01.000]This is the first line." {
hasFirstTimeline = true
}
}
if !hasTitleLine {
t.Errorf("Expected title line '[ti:Test LRC File]' not found")
}
if !hasFirstTimeline {
t.Errorf("Expected timeline line '[00:01.000]This is the first line.' not found")
}
}
func TestGenerate_FileError(t *testing.T) {
// Create test lyrics
lyrics := model.Lyrics{
Metadata: map[string]string{
"ti": "Test LRC File",
},
Timeline: []model.Timestamp{
{Hours: 0, Minutes: 0, Seconds: 1, Milliseconds: 0},
},
Content: []string{
"This is a test line.",
},
}
// Test with invalid path
err := Generate(lyrics, "/nonexistent/directory/file.lrc")
if err == nil {
t.Error("Expected error when generating to invalid path, got nil")
}
}
func TestGenerate_EdgeCases(t *testing.T) {
// Test with empty lyrics
emptyLyrics := model.Lyrics{
Metadata: map[string]string{
"ti": "Empty Test",
},
}
tempDir := t.TempDir()
emptyFile := filepath.Join(tempDir, "empty_output.lrc")
err := Generate(emptyLyrics, emptyFile)
if err != nil {
t.Fatalf("Generate failed with empty lyrics: %v", err)
}
// Verify content has metadata but no timeline entries
content, err := os.ReadFile(emptyFile)
if err != nil {
t.Fatalf("Failed to read empty output file: %v", err)
}
if !strings.Contains(string(content), "[ti:Empty Test]") {
t.Errorf("Expected metadata in empty lyrics output, not found")
}
// Test with unequal timeline and content lengths
unequalLyrics := model.Lyrics{
Timeline: []model.Timestamp{
{Hours: 0, Minutes: 0, Seconds: 1, Milliseconds: 0},
{Hours: 0, Minutes: 0, Seconds: 5, Milliseconds: 0},
},
Content: []string{
"This is the only content line.",
},
}
unequalFile := filepath.Join(tempDir, "unequal_output.lrc")
err = Generate(unequalLyrics, unequalFile)
if err != nil {
t.Fatalf("Generate failed with unequal lyrics: %v", err)
}
// Should only generate for the entries that have both timeline and content
content, err = os.ReadFile(unequalFile)
if err != nil {
t.Fatalf("Failed to read unequal output file: %v", err)
}
lines := strings.Split(string(content), "\n")
timelineLines := 0
for _, line := range lines {
if strings.HasPrefix(line, "[") && strings.Contains(line, "]") &&
strings.Contains(line, ":") && strings.Contains(line, ".") {
timelineLines++
}
}
if timelineLines > 1 {
t.Errorf("Expected only 1 timeline entry for unequal lyrics, got %d", timelineLines)
}
}

View file

@ -1,518 +0,0 @@
package lrc
import (
"os"
"path/filepath"
"strings"
"testing"
"sub-cli/internal/model"
)
func TestParse(t *testing.T) {
// Create a temporary test file
content := `[ti:Test LRC File]
[ar:Test Artist]
[al:Test Album]
[by:Test Creator]
[00:01.00]This is the first line.
[00:05.00]This is the second line.
[00:09.50]This is the third line.
`
tempDir := t.TempDir()
testFile := filepath.Join(tempDir, "test.lrc")
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
// Test parsing
lyrics, err := Parse(testFile)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Verify results
if len(lyrics.Timeline) != 3 {
t.Errorf("Expected 3 timeline entries, got %d", len(lyrics.Timeline))
}
if len(lyrics.Content) != 3 {
t.Errorf("Expected 3 content entries, got %d", len(lyrics.Content))
}
// Check metadata
if lyrics.Metadata["ti"] != "Test LRC File" {
t.Errorf("Expected title 'Test LRC File', got '%s'", lyrics.Metadata["ti"])
}
if lyrics.Metadata["ar"] != "Test Artist" {
t.Errorf("Expected artist 'Test Artist', got '%s'", lyrics.Metadata["ar"])
}
if lyrics.Metadata["al"] != "Test Album" {
t.Errorf("Expected album 'Test Album', got '%s'", lyrics.Metadata["al"])
}
if lyrics.Metadata["by"] != "Test Creator" {
t.Errorf("Expected creator 'Test Creator', got '%s'", lyrics.Metadata["by"])
}
// Check first timeline entry
if lyrics.Timeline[0].Hours != 0 || lyrics.Timeline[0].Minutes != 0 ||
lyrics.Timeline[0].Seconds != 1 || lyrics.Timeline[0].Milliseconds != 0 {
t.Errorf("First entry time: expected 00:01.00, got %+v", lyrics.Timeline[0])
}
// Check third timeline entry
if lyrics.Timeline[2].Hours != 0 || lyrics.Timeline[2].Minutes != 0 ||
lyrics.Timeline[2].Seconds != 9 || lyrics.Timeline[2].Milliseconds != 500 {
t.Errorf("Third entry time: expected 00:09.50, got %+v", lyrics.Timeline[2])
}
// Check content
if lyrics.Content[0] != "This is the first line." {
t.Errorf("First entry content: expected 'This is the first line.', got '%s'", lyrics.Content[0])
}
}
func TestGenerate(t *testing.T) {
// Create test lyrics
lyrics := model.Lyrics{
Metadata: map[string]string{
"ti": "Test LRC File",
"ar": "Test Artist",
},
Timeline: []model.Timestamp{
{Hours: 0, Minutes: 0, Seconds: 1, Milliseconds: 0},
{Hours: 0, Minutes: 0, Seconds: 5, Milliseconds: 0},
},
Content: []string{
"This is the first line.",
"This is the second line.",
},
}
// Generate LRC file
tempDir := t.TempDir()
outputFile := filepath.Join(tempDir, "output.lrc")
err := Generate(lyrics, outputFile)
if err != nil {
t.Fatalf("Generate failed: %v", err)
}
// Verify generated content
content, err := os.ReadFile(outputFile)
if err != nil {
t.Fatalf("Failed to read output file: %v", err)
}
// Check content
lines := strings.Split(string(content), "\n")
if len(lines) < 4 {
t.Fatalf("Expected at least 4 lines, got %d", len(lines))
}
hasTitleLine := false
hasFirstTimeline := false
for _, line := range lines {
if line == "[ti:Test LRC File]" {
hasTitleLine = true
}
if line == "[00:01.000]This is the first line." {
hasFirstTimeline = true
}
}
if !hasTitleLine {
t.Errorf("Expected title line '[ti:Test LRC File]' not found")
}
if !hasFirstTimeline {
t.Errorf("Expected timeline line '[00:01.000]This is the first line.' not found")
}
}
func TestConvertToSubtitle(t *testing.T) {
// Create a temporary test file
content := `[ti:Test LRC File]
[ar:Test Artist]
[00:01.00]This is the first line.
[00:05.00]This is the second line.
`
tempDir := t.TempDir()
testFile := filepath.Join(tempDir, "test.lrc")
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
// Convert to subtitle
subtitle, err := ConvertToSubtitle(testFile)
if err != nil {
t.Fatalf("ConvertToSubtitle failed: %v", err)
}
// Check result
if subtitle.Format != "lrc" {
t.Errorf("Expected format 'lrc', got '%s'", subtitle.Format)
}
if subtitle.Title != "Test LRC File" {
t.Errorf("Expected title 'Test LRC File', got '%s'", subtitle.Title)
}
if len(subtitle.Entries) != 2 {
t.Errorf("Expected 2 entries, got %d", len(subtitle.Entries))
}
// Check first entry
if subtitle.Entries[0].Text != "This is the first line." {
t.Errorf("Expected first entry text 'This is the first line.', got '%s'", subtitle.Entries[0].Text)
}
// Check metadata
if subtitle.Metadata["ar"] != "Test Artist" {
t.Errorf("Expected metadata 'ar' to be 'Test Artist', got '%s'", subtitle.Metadata["ar"])
}
}
func TestConvertFromSubtitle(t *testing.T) {
// Create test subtitle
subtitle := model.NewSubtitle()
subtitle.Format = "lrc"
subtitle.Title = "Test LRC File"
subtitle.Metadata["ar"] = "Test Artist"
entry1 := model.NewSubtitleEntry()
entry1.StartTime = model.Timestamp{Hours: 0, Minutes: 0, Seconds: 1, Milliseconds: 0}
entry1.EndTime = model.Timestamp{Hours: 0, Minutes: 0, Seconds: 4, Milliseconds: 0}
entry1.Text = "This is the first line."
entry2 := model.NewSubtitleEntry()
entry2.StartTime = model.Timestamp{Hours: 0, Minutes: 0, Seconds: 5, Milliseconds: 0}
entry2.EndTime = model.Timestamp{Hours: 0, Minutes: 0, Seconds: 8, Milliseconds: 0}
entry2.Text = "This is the second line."
subtitle.Entries = append(subtitle.Entries, entry1, entry2)
// Convert from subtitle to LRC
tempDir := t.TempDir()
outputFile := filepath.Join(tempDir, "output.lrc")
err := ConvertFromSubtitle(subtitle, outputFile)
if err != nil {
t.Fatalf("ConvertFromSubtitle failed: %v", err)
}
// Verify by parsing back
lyrics, err := Parse(outputFile)
if err != nil {
t.Fatalf("Failed to parse output file: %v", err)
}
if len(lyrics.Timeline) != 2 {
t.Errorf("Expected 2 entries, got %d", len(lyrics.Timeline))
}
if lyrics.Content[0] != "This is the first line." {
t.Errorf("Expected first entry content 'This is the first line.', got '%s'", lyrics.Content[0])
}
if lyrics.Metadata["ti"] != "Test LRC File" {
t.Errorf("Expected title metadata 'Test LRC File', got '%s'", lyrics.Metadata["ti"])
}
}
func TestFormat(t *testing.T) {
// Create test LRC file with inconsistent timestamp formatting
content := `[ti:Test LRC File]
[ar:Test Artist]
[0:1.0]This is the first line.
[0:5]This is the second line.
`
tempDir := t.TempDir()
testFile := filepath.Join(tempDir, "test.lrc")
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
// Format the file
err := Format(testFile)
if err != nil {
t.Fatalf("Format failed: %v", err)
}
// Verify by parsing back
lyrics, err := Parse(testFile)
if err != nil {
t.Fatalf("Failed to parse formatted file: %v", err)
}
// Check that timestamps are formatted correctly
if lyrics.Timeline[0].Seconds != 1 || lyrics.Timeline[0].Milliseconds != 0 {
t.Errorf("Expected first timestamp to be 00:01.000, got %+v", lyrics.Timeline[0])
}
// Verify metadata is preserved
if lyrics.Metadata["ti"] != "Test LRC File" {
t.Errorf("Expected title 'Test LRC File', got '%s'", lyrics.Metadata["ti"])
}
}
func TestParseTimestamp(t *testing.T) {
testCases := []struct {
name string
input string
expected model.Timestamp
hasError bool
}{
{
name: "Simple minute and second",
input: "01:30",
expected: model.Timestamp{Hours: 0, Minutes: 1, Seconds: 30, Milliseconds: 0},
hasError: false,
},
{
name: "With milliseconds (1 digit)",
input: "01:30.5",
expected: model.Timestamp{Hours: 0, Minutes: 1, Seconds: 30, Milliseconds: 500},
hasError: false,
},
{
name: "With milliseconds (2 digits)",
input: "01:30.75",
expected: model.Timestamp{Hours: 0, Minutes: 1, Seconds: 30, Milliseconds: 750},
hasError: false,
},
{
name: "With milliseconds (3 digits)",
input: "01:30.123",
expected: model.Timestamp{Hours: 0, Minutes: 1, Seconds: 30, Milliseconds: 123},
hasError: false,
},
{
name: "With hours, minutes, seconds",
input: "01:30:45",
expected: model.Timestamp{Hours: 1, Minutes: 30, Seconds: 45, Milliseconds: 0},
hasError: false,
},
{
name: "With hours, minutes, seconds and milliseconds",
input: "01:30:45.5",
expected: model.Timestamp{Hours: 1, Minutes: 30, Seconds: 45, Milliseconds: 500},
hasError: false,
},
{
name: "Invalid format (single number)",
input: "123",
expected: model.Timestamp{},
hasError: true,
},
{
name: "Invalid format (too many parts)",
input: "01:30:45:67",
expected: model.Timestamp{},
hasError: true,
},
{
name: "Invalid minute (not a number)",
input: "aa:30",
expected: model.Timestamp{},
hasError: true,
},
{
name: "Invalid second (not a number)",
input: "01:bb",
expected: model.Timestamp{},
hasError: true,
},
{
name: "Invalid millisecond (not a number)",
input: "01:30.cc",
expected: model.Timestamp{},
hasError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := ParseTimestamp(tc.input)
if tc.hasError && err == nil {
t.Errorf("Expected error for input '%s', but got none", tc.input)
}
if !tc.hasError && err != nil {
t.Errorf("Unexpected error for input '%s': %v", tc.input, err)
}
if !tc.hasError && result != tc.expected {
t.Errorf("For input '%s', expected %+v, got %+v", tc.input, tc.expected, result)
}
})
}
}
func TestParse_FileErrors(t *testing.T) {
// Test with non-existent file
_, err := Parse("/nonexistent/file.lrc")
if err == nil {
t.Error("Expected error when parsing non-existent file, got nil")
}
}
func TestParse_EdgeCases(t *testing.T) {
// Test with empty file
tempDir := t.TempDir()
emptyFile := filepath.Join(tempDir, "empty.lrc")
if err := os.WriteFile(emptyFile, []byte{}, 0644); err != nil {
t.Fatalf("Failed to create empty test file: %v", err)
}
lyrics, err := Parse(emptyFile)
if err != nil {
t.Fatalf("Parse failed on empty file: %v", err)
}
if len(lyrics.Timeline) != 0 || len(lyrics.Content) != 0 {
t.Errorf("Expected empty lyrics for empty file, got %d timeline entries and %d content entries",
len(lyrics.Timeline), len(lyrics.Content))
}
// Test with invalid timestamps
invalidFile := filepath.Join(tempDir, "invalid.lrc")
content := `[ti:Test LRC File]
[ar:Test Artist]
[invalidtime]This should be ignored.
[00:01.00]This is a valid line.
`
if err := os.WriteFile(invalidFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create invalid test file: %v", err)
}
lyrics, err = Parse(invalidFile)
if err != nil {
t.Fatalf("Parse failed on file with invalid timestamps: %v", err)
}
if len(lyrics.Timeline) != 1 || len(lyrics.Content) != 1 {
t.Errorf("Expected 1 valid timeline entry, got %d timeline entries and %d content entries",
len(lyrics.Timeline), len(lyrics.Content))
}
// Test with timestamp-only lines (no content)
timestampOnlyFile := filepath.Join(tempDir, "timestamp_only.lrc")
content = `[ti:Test LRC File]
[ar:Test Artist]
[00:01.00]
[00:05.00]This has content.
`
if err := os.WriteFile(timestampOnlyFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create timestamp-only test file: %v", err)
}
lyrics, err = Parse(timestampOnlyFile)
if err != nil {
t.Fatalf("Parse failed on file with timestamp-only lines: %v", err)
}
if len(lyrics.Timeline) != 1 || len(lyrics.Content) != 1 {
t.Errorf("Expected 1 valid entry (ignoring empty content), got %d timeline entries and %d content entries",
len(lyrics.Timeline), len(lyrics.Content))
}
}
func TestGenerate_FileError(t *testing.T) {
// Create test lyrics
lyrics := model.Lyrics{
Metadata: map[string]string{
"ti": "Test LRC File",
},
Timeline: []model.Timestamp{
{Hours: 0, Minutes: 0, Seconds: 1, Milliseconds: 0},
},
Content: []string{
"This is a test line.",
},
}
// Test with invalid path
err := Generate(lyrics, "/nonexistent/directory/file.lrc")
if err == nil {
t.Error("Expected error when generating to invalid path, got nil")
}
}
func TestFormat_FileError(t *testing.T) {
// Test with non-existent file
err := Format("/nonexistent/file.lrc")
if err == nil {
t.Error("Expected error when formatting non-existent file, got nil")
}
}
func TestConvertToSubtitle_FileError(t *testing.T) {
// Test with non-existent file
_, err := ConvertToSubtitle("/nonexistent/file.lrc")
if err == nil {
t.Error("Expected error when converting non-existent file, got nil")
}
}
func TestConvertToSubtitle_EdgeCases(t *testing.T) {
// Test with empty lyrics (no content/timeline)
tempDir := t.TempDir()
emptyLyricsFile := filepath.Join(tempDir, "empty_lyrics.lrc")
content := `[ti:Test LRC File]
[ar:Test Artist]
`
if err := os.WriteFile(emptyLyricsFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create empty lyrics test file: %v", err)
}
subtitle, err := ConvertToSubtitle(emptyLyricsFile)
if err != nil {
t.Fatalf("ConvertToSubtitle failed on empty lyrics: %v", err)
}
if len(subtitle.Entries) != 0 {
t.Errorf("Expected 0 entries for empty lyrics, got %d", len(subtitle.Entries))
}
if subtitle.Title != "Test LRC File" {
t.Errorf("Expected title 'Test LRC File', got '%s'", subtitle.Title)
}
// Test with more content than timeline entries
moreContentFile := filepath.Join(tempDir, "more_content.lrc")
content = `[ti:Test LRC File]
[00:01.00]This has a timestamp.
This doesn't have a timestamp but is content.
`
if err := os.WriteFile(moreContentFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create more content test file: %v", err)
}
subtitle, err = ConvertToSubtitle(moreContentFile)
if err != nil {
t.Fatalf("ConvertToSubtitle failed on file with more content than timestamps: %v", err)
}
if len(subtitle.Entries) != 1 {
t.Errorf("Expected 1 entry (only the one with timestamp), got %d", len(subtitle.Entries))
}
}
func TestConvertFromSubtitle_FileError(t *testing.T) {
// Create simple subtitle
subtitle := model.NewSubtitle()
subtitle.Entries = append(subtitle.Entries, model.NewSubtitleEntry())
// Test with invalid path
err := ConvertFromSubtitle(subtitle, "/nonexistent/directory/file.lrc")
if err == nil {
t.Error("Expected error when converting to invalid path, got nil")
}
}

View file

@ -0,0 +1,185 @@
package lrc
import (
"os"
"path/filepath"
"testing"
)
func TestParse(t *testing.T) {
// Create a temporary test file
content := `[ti:Test LRC File]
[ar:Test Artist]
[al:Test Album]
[by:Test Creator]
[00:01.00]This is the first line.
[00:05.00]This is the second line.
[00:09.50]This is the third line.
`
tempDir := t.TempDir()
testFile := filepath.Join(tempDir, "test.lrc")
if err := os.WriteFile(testFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
// Test parsing
lyrics, err := Parse(testFile)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Verify results
if len(lyrics.Timeline) != 3 {
t.Errorf("Expected 3 timeline entries, got %d", len(lyrics.Timeline))
}
if len(lyrics.Content) != 3 {
t.Errorf("Expected 3 content entries, got %d", len(lyrics.Content))
}
// Check metadata
if lyrics.Metadata["ti"] != "Test LRC File" {
t.Errorf("Expected title 'Test LRC File', got '%s'", lyrics.Metadata["ti"])
}
if lyrics.Metadata["ar"] != "Test Artist" {
t.Errorf("Expected artist 'Test Artist', got '%s'", lyrics.Metadata["ar"])
}
if lyrics.Metadata["al"] != "Test Album" {
t.Errorf("Expected album 'Test Album', got '%s'", lyrics.Metadata["al"])
}
if lyrics.Metadata["by"] != "Test Creator" {
t.Errorf("Expected creator 'Test Creator', got '%s'", lyrics.Metadata["by"])
}
// Check first timeline entry
if lyrics.Timeline[0].Hours != 0 || lyrics.Timeline[0].Minutes != 0 ||
lyrics.Timeline[0].Seconds != 1 || lyrics.Timeline[0].Milliseconds != 0 {
t.Errorf("First entry time: expected 00:01.00, got %+v", lyrics.Timeline[0])
}
// Check third timeline entry
if lyrics.Timeline[2].Hours != 0 || lyrics.Timeline[2].Minutes != 0 ||
lyrics.Timeline[2].Seconds != 9 || lyrics.Timeline[2].Milliseconds != 500 {
t.Errorf("Third entry time: expected 00:09.50, got %+v", lyrics.Timeline[2])
}
// Check content
if lyrics.Content[0] != "This is the first line." {
t.Errorf("First entry content: expected 'This is the first line.', got '%s'", lyrics.Content[0])
}
}
func TestParse_FileErrors(t *testing.T) {
// Test with non-existent file
_, err := Parse("/nonexistent/file.lrc")
if err == nil {
t.Error("Expected error when parsing non-existent file, got nil")
}
}
func TestParse_EdgeCases(t *testing.T) {
// Test with empty file
tempDir := t.TempDir()
emptyFile := filepath.Join(tempDir, "empty.lrc")
if err := os.WriteFile(emptyFile, []byte(""), 0644); err != nil {
t.Fatalf("Failed to create empty file: %v", err)
}
lyrics, err := Parse(emptyFile)
if err != nil {
t.Fatalf("Parse failed with empty file: %v", err)
}
if len(lyrics.Timeline) != 0 || len(lyrics.Content) != 0 {
t.Errorf("Expected empty lyrics for empty file, got %d timeline and %d content",
len(lyrics.Timeline), len(lyrics.Content))
}
// Test with metadata only
metadataFile := filepath.Join(tempDir, "metadata.lrc")
metadataContent := `[ti:Test Title]
[ar:Test Artist]
[al:Test Album]
`
if err := os.WriteFile(metadataFile, []byte(metadataContent), 0644); err != nil {
t.Fatalf("Failed to create metadata file: %v", err)
}
lyrics, err = Parse(metadataFile)
if err != nil {
t.Fatalf("Parse failed with metadata-only file: %v", err)
}
if lyrics.Metadata["ti"] != "Test Title" {
t.Errorf("Expected title 'Test Title', got '%s'", lyrics.Metadata["ti"])
}
if len(lyrics.Timeline) != 0 || len(lyrics.Content) != 0 {
t.Errorf("Expected empty timeline/content for metadata-only file, got %d timeline and %d content",
len(lyrics.Timeline), len(lyrics.Content))
}
// Test with invalid metadata
invalidMetadataFile := filepath.Join(tempDir, "invalid_metadata.lrc")
invalidMetadata := `[ti:Test Title
[ar:Test Artist]
[00:01.00]This is a valid line.
`
if err := os.WriteFile(invalidMetadataFile, []byte(invalidMetadata), 0644); err != nil {
t.Fatalf("Failed to create invalid metadata file: %v", err)
}
lyrics, err = Parse(invalidMetadataFile)
if err != nil {
t.Fatalf("Parse failed with invalid metadata file: %v", err)
}
if lyrics.Metadata["ti"] != "" { // Should ignore invalid metadata
t.Errorf("Expected empty title for invalid metadata, got '%s'", lyrics.Metadata["ti"])
}
if len(lyrics.Timeline) != 1 || len(lyrics.Content) != 1 {
t.Errorf("Expected 1 timeline/content entry for file with invalid metadata, got %d timeline and %d content",
len(lyrics.Timeline), len(lyrics.Content))
}
// Test with invalid timestamp format
invalidFile := filepath.Join(tempDir, "invalid.lrc")
content := `[ti:Test LRC File]
[ar:Test Artist]
[invalidtime]This should be ignored.
[00:01.00]This is a valid line.
`
if err := os.WriteFile(invalidFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create invalid test file: %v", err)
}
lyrics, err = Parse(invalidFile)
if err != nil {
t.Fatalf("Parse failed on file with invalid timestamps: %v", err)
}
if len(lyrics.Timeline) != 1 || len(lyrics.Content) != 1 {
t.Errorf("Expected 1 valid timeline entry, got %d timeline entries and %d content entries",
len(lyrics.Timeline), len(lyrics.Content))
}
// Test with timestamp-only lines (no content)
timestampOnlyFile := filepath.Join(tempDir, "timestamp_only.lrc")
content = `[ti:Test LRC File]
[ar:Test Artist]
[00:01.00]
[00:05.00]This has content.
`
if err := os.WriteFile(timestampOnlyFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create timestamp-only test file: %v", err)
}
lyrics, err = Parse(timestampOnlyFile)
if err != nil {
t.Fatalf("Parse failed on file with timestamp-only lines: %v", err)
}
if len(lyrics.Timeline) != 1 || len(lyrics.Content) != 1 {
t.Errorf("Expected 1 valid entry (ignoring empty content), got %d timeline entries and %d content entries",
len(lyrics.Timeline), len(lyrics.Content))
}
}

View file

@ -0,0 +1,163 @@
package lrc
import (
"testing"
"sub-cli/internal/model"
)
func TestParseTimestamp(t *testing.T) {
testCases := []struct {
name string
input string
expected model.Timestamp
valid bool
}{
{
name: "Simple minute and second",
input: "[01:30]",
expected: model.Timestamp{
Hours: 0,
Minutes: 1,
Seconds: 30,
Milliseconds: 0,
},
valid: true,
},
{
name: "With milliseconds",
input: "[01:30.500]",
expected: model.Timestamp{
Hours: 0,
Minutes: 1,
Seconds: 30,
Milliseconds: 500,
},
valid: true,
},
{
name: "With hours",
input: "[01:30:45.500]",
expected: model.Timestamp{
Hours: 1,
Minutes: 30,
Seconds: 45,
Milliseconds: 500,
},
valid: true,
},
{
name: "Zero time",
input: "[00:00.000]",
expected: model.Timestamp{
Hours: 0,
Minutes: 0,
Seconds: 0,
Milliseconds: 0,
},
valid: true,
},
{
name: "Invalid format - no brackets",
input: "01:30",
expected: model.Timestamp{
Hours: 0,
Minutes: 1,
Seconds: 30,
Milliseconds: 0,
},
valid: true, // ParseTimestamp automatically strips brackets, so it will parse this without brackets
},
{
name: "Invalid format - wrong brackets",
input: "(01:30)",
expected: model.Timestamp{},
valid: false,
},
{
name: "Invalid format - no time",
input: "[]",
expected: model.Timestamp{},
valid: false,
},
{
name: "Invalid format - text in brackets",
input: "[text]",
expected: model.Timestamp{},
valid: false,
},
{
name: "Invalid format - incomplete time",
input: "[01:]",
expected: model.Timestamp{},
valid: false,
},
{
name: "Invalid format - incomplete time with milliseconds",
input: "[01:.500]",
expected: model.Timestamp{},
valid: false,
},
{
name: "Metadata tag",
input: "[ti:Title]",
expected: model.Timestamp{},
valid: false,
},
{
name: "With milliseconds - alternative format using comma",
input: "[01:30.500]", // Use period instead of comma since our parser doesn't handle comma
expected: model.Timestamp{
Hours: 0,
Minutes: 1,
Seconds: 30,
Milliseconds: 500,
},
valid: true,
},
{
name: "With double-digit milliseconds",
input: "[01:30.50]",
expected: model.Timestamp{
Hours: 0,
Minutes: 1,
Seconds: 30,
Milliseconds: 500,
},
valid: true,
},
{
name: "With single-digit milliseconds",
input: "[01:30.5]",
expected: model.Timestamp{
Hours: 0,
Minutes: 1,
Seconds: 30,
Milliseconds: 500,
},
valid: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
timestamp, err := ParseTimestamp(tc.input)
if (err == nil) != tc.valid {
t.Errorf("Expected valid=%v, got valid=%v (err=%v)", tc.valid, err == nil, err)
return
}
if !tc.valid {
return // No need to check further for invalid cases
}
if timestamp.Hours != tc.expected.Hours ||
timestamp.Minutes != tc.expected.Minutes ||
timestamp.Seconds != tc.expected.Seconds ||
timestamp.Milliseconds != tc.expected.Milliseconds {
t.Errorf("Expected timestamp %+v, got %+v", tc.expected, timestamp)
}
})
}
}