feat: Sample Tag Viewer — read-only tag browser for AKAI disk images
Backend: - extract.go: listTags() navigates to volume, runs akaiutil lstags - extract.go: parseTags() parses tag name/index from lstags output - serve.go: GET /api/disk/partitions — list partitions on an ISO - serve.go: GET /api/disk/volumes — list volumes on a partition - serve.go: GET /api/volume/tags — list tags on a volume Web UI: - Tag Viewer section with ISO path input and Browse button - Partition selector buttons - Volume grid with click-to-select - Tag chips with color-coded dots (20-color palette) Tests: - 6 parseTags tests (basic, spaces, defaults, no tags, empty, no-type) - 1 listTags integration test - 4 API endpoint tests (partitions, missing-ISO, missing-params, tags) Karpathy: read-only view first — no write operations yet (settagi, clrtagi)
This commit is contained in:
+113
@@ -368,3 +368,116 @@ func TestRunExtraction_CreatesVolumeDirs(t *testing.T) {
|
||||
t.Error("expected lcd / to be in commands")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTags(t *testing.T) {
|
||||
t.Run("parses basic tag list", func(t *testing.T) {
|
||||
out := `
|
||||
tag name
|
||||
-----------------
|
||||
01 Drums
|
||||
02 Bass
|
||||
03 FX
|
||||
04 Vox
|
||||
05 Percussion
|
||||
-----------------
|
||||
`
|
||||
tags := parseTags(out)
|
||||
if len(tags) != 5 {
|
||||
t.Fatalf("expected 5 tags, got %d", len(tags))
|
||||
}
|
||||
if tags[0].Index != 1 || tags[0].Name != "Drums" {
|
||||
t.Errorf("tag 0: got {%d %s}, want {1 Drums}", tags[0].Index, tags[0].Name)
|
||||
}
|
||||
if tags[4].Index != 5 || tags[4].Name != "Percussion" {
|
||||
t.Errorf("tag 4: got {%d %s}, want {5 Percussion}", tags[4].Index, tags[4].Name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("parses tags with spaces in names", func(t *testing.T) {
|
||||
out := `
|
||||
tag name
|
||||
-----------------
|
||||
01 Kick Drum
|
||||
02 Hi Hat
|
||||
03 Snare Drum
|
||||
-----------------
|
||||
`
|
||||
tags := parseTags(out)
|
||||
if len(tags) != 3 {
|
||||
t.Fatalf("expected 3 tags, got %d", len(tags))
|
||||
}
|
||||
if tags[0].Name != "Kick Drum" {
|
||||
t.Errorf("got %q, want 'Kick Drum'", tags[0].Name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("handles default tag names from akaiutil", func(t *testing.T) {
|
||||
out := `
|
||||
tag name
|
||||
-----------------
|
||||
01 TAG A
|
||||
02 TAG B
|
||||
03 MyTag
|
||||
-----------------
|
||||
`
|
||||
tags := parseTags(out)
|
||||
if len(tags) != 3 {
|
||||
t.Fatalf("expected 3 tags, got %d", len(tags))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no tags present message returns empty", func(t *testing.T) {
|
||||
out := "no tags present\n"
|
||||
tags := parseTags(out)
|
||||
if len(tags) != 0 {
|
||||
t.Errorf("expected 0 tags, got %d", len(tags))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("handles empty output", func(t *testing.T) {
|
||||
tags := parseTags("")
|
||||
if len(tags) != 0 {
|
||||
t.Errorf("expected 0 tags, got %d", len(tags))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("handles no tags in partition type", func(t *testing.T) {
|
||||
out := "no tags in this partition type\n"
|
||||
tags := parseTags(out)
|
||||
if len(tags) != 0 {
|
||||
t.Errorf("expected 0 tags, got %d", len(tags))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestListTags_Integration(t *testing.T) {
|
||||
origRun := runAkaiutil
|
||||
defer func() { runAkaiutil = origRun }()
|
||||
|
||||
runAkaiutil = func(isoPath, commands string) (string, error) {
|
||||
return `
|
||||
/disk0/A > lstags
|
||||
|
||||
tag name
|
||||
-----------------
|
||||
01 Drums
|
||||
02 Bass
|
||||
03 FX
|
||||
-----------------
|
||||
`, nil
|
||||
}
|
||||
|
||||
tags, err := listTags("fake.iso", "A", "SomeVolume")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(tags) != 3 {
|
||||
t.Errorf("expected 3 tags, got %d", len(tags))
|
||||
}
|
||||
if tags[0].Name != "Drums" {
|
||||
t.Errorf("expected Drums, got %s", tags[0].Name)
|
||||
}
|
||||
if tags[2].Index != 3 || tags[2].Name != "FX" {
|
||||
t.Errorf("tag 2: got {%d %s}, want {3 FX}", tags[2].Index, tags[2].Name)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user