Fork channel

Create a new channel as a copy of main.

Rename channel

Rename main to:

Delete channel

Delete main? This cannot be undone.

calls_classify_nav_test.go
package calls

import (
	"testing"

	"skraak/datafile"
)

// --- Navigation: NextSegment / PrevSegment ---

func TestNextSegment(t *testing.T) {
	df := &datafile.DataFile{
		FilePath: "/test/f1.data",
		Segments: []*datafile.Segment{
			{StartTime: 0, EndTime: 10},
			{StartTime: 10, EndTime: 20},
		},
	}
	df2 := &datafile.DataFile{
		FilePath: "/test/f2.data",
		Segments: []*datafile.Segment{
			{StartTime: 0, EndTime: 10},
		},
	}

	state := NewClassifyState(ClassifyConfig{Certainty: -1}, []*datafile.DataFile{df, df2})

	// Start at file 0, segment 0
	if state.FileIdx != 0 || state.SegmentIdx != 0 {
		t.Fatalf("initial: file=%d seg=%d", state.FileIdx, state.SegmentIdx)
	}

	// Advance within same file
	if !state.NextSegment() {
		t.Error("next within file should succeed")
	}
	if state.FileIdx != 0 || state.SegmentIdx != 1 {
		t.Errorf("after next: file=%d seg=%d", state.FileIdx, state.SegmentIdx)
	}

	// Advance to next file
	if !state.NextSegment() {
		t.Error("next to next file should succeed")
	}
	if state.FileIdx != 1 || state.SegmentIdx != 0 {
		t.Errorf("after next file: file=%d seg=%d", state.FileIdx, state.SegmentIdx)
	}

	// At end
	if state.NextSegment() {
		t.Error("should fail at end")
	}
}

func TestPrevSegment(t *testing.T) {
	df := &datafile.DataFile{
		FilePath: "/test/f1.data",
		Segments: []*datafile.Segment{
			{StartTime: 0, EndTime: 10},
		},
	}
	df2 := &datafile.DataFile{
		FilePath: "/test/f2.data",
		Segments: []*datafile.Segment{
			{StartTime: 0, EndTime: 10},
			{StartTime: 10, EndTime: 20},
		},
	}

	state := NewClassifyState(ClassifyConfig{Certainty: -1}, []*datafile.DataFile{df, df2})
	state.FileIdx = 1
	state.SegmentIdx = 1

	// Prev within same file
	if !state.PrevSegment() {
		t.Error("prev within file should succeed")
	}
	if state.FileIdx != 1 || state.SegmentIdx != 0 {
		t.Errorf("after prev: file=%d seg=%d", state.FileIdx, state.SegmentIdx)
	}

	// Prev to previous file
	if !state.PrevSegment() {
		t.Error("prev to previous file should succeed")
	}
	if state.FileIdx != 0 || state.SegmentIdx != 0 {
		t.Errorf("after prev file: file=%d seg=%d", state.FileIdx, state.SegmentIdx)
	}

	// At start
	if state.PrevSegment() {
		t.Error("should fail at start")
	}
}

// --- buildClassifyState goto logic ---

func TestBuildClassifyState_GotoFound(t *testing.T) {
	df1 := &datafile.DataFile{FilePath: "/test/alpha.data", Segments: []*datafile.Segment{{}}}
	df2 := &datafile.DataFile{FilePath: "/test/beta.data", Segments: []*datafile.Segment{{}}}

	segs := [][]*datafile.Segment{df1.Segments, df2.Segments}

	state, err := buildClassifyState(ClassifyConfig{Goto: "beta.data"}, []*datafile.DataFile{df1, df2}, segs, 0)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if state.FileIdx != 1 {
		t.Errorf("FileIdx=%d, want 1", state.FileIdx)
	}
}

func TestBuildClassifyState_GotoNotFound(t *testing.T) {
	df1 := &datafile.DataFile{FilePath: "/test/alpha.data", Segments: []*datafile.Segment{{}}}
	segs := [][]*datafile.Segment{df1.Segments}

	_, err := buildClassifyState(ClassifyConfig{Goto: "missing.data"}, []*datafile.DataFile{df1}, segs, 0)
	if err == nil {
		t.Error("expected error for missing goto file")
	}
}

func TestBuildClassifyState_NoGoto(t *testing.T) {
	df1 := &datafile.DataFile{FilePath: "/test/alpha.data", Segments: []*datafile.Segment{{}}}
	segs := [][]*datafile.Segment{df1.Segments}

	state, err := buildClassifyState(ClassifyConfig{}, []*datafile.DataFile{df1}, segs, 0)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if state.FileIdx != 0 {
		t.Errorf("FileIdx=%d, want 0", state.FileIdx)
	}
}

// --- FormatLabels ---

func TestFormatLabels(t *testing.T) {
	labels := []*datafile.Label{
		{Species: "Kiwi", CallType: "Duet", Certainty: 90, Filter: "model-1.0"},
		{Species: "Tomtit", Certainty: 70, Filter: "model-2.0"},
	}

	t.Run("no filter", func(t *testing.T) {
		got := FormatLabels(labels, "")
		if got == "" {
			t.Error("expected non-empty output")
		}
		// Both labels should appear
		if !contains(got, "Kiwi/Duet") {
			t.Errorf("expected Kiwi/Duet in %q", got)
		}
		if !contains(got, "Tomtit") {
			t.Errorf("expected Tomtit in %q", got)
		}
	})

	t.Run("with filter", func(t *testing.T) {
		got := FormatLabels(labels, "model-1.0")
		if !contains(got, "Kiwi") {
			t.Errorf("expected Kiwi in %q", got)
		}
		if contains(got, "Tomtit") {
			t.Errorf("Tomtit should be filtered out in %q", got)
		}
	})

	t.Run("with comment", func(t *testing.T) {
		labels := []*datafile.Label{
			{Species: "Kiwi", Certainty: 100, Filter: "f1", Comment: "nice call"},
		}
		got := FormatLabels(labels, "")
		if !contains(got, `"nice call"`) {
			t.Errorf("expected comment in %q", got)
		}
	})
}

// --- Bookmark navigation ---

func TestBookmarkNavigation(t *testing.T) {
	df := &datafile.DataFile{
		FilePath: "/test/f1.data",
		Meta:     &datafile.DataMeta{},
		Segments: []*datafile.Segment{
			{Labels: []*datafile.Label{{Species: "Kiwi", Filter: "f", Bookmark: true}}},
			{Labels: []*datafile.Label{{Species: "Tomtit", Filter: "f"}}},
			{Labels: []*datafile.Label{{Species: "Roroa", Filter: "f", Bookmark: true}}},
		},
	}

	state := NewClassifyState(ClassifyConfig{Filter: "f", Certainty: -1}, []*datafile.DataFile{df})

	// Start at segment 0 (has bookmark)
	if !state.HasBookmark() {
		t.Error("segment 0 should have bookmark")
	}

	// NextBookmark should go to segment 2
	if !state.NextBookmark() {
		t.Error("NextBookmark should find bookmark at segment 2")
	}
	if state.SegmentIdx != 2 {
		t.Errorf("after NextBookmark: seg=%d, want 2", state.SegmentIdx)
	}

	// PrevBookmark should go back to segment 0
	if !state.PrevBookmark() {
		t.Error("PrevBookmark should find bookmark at segment 0")
	}
	if state.SegmentIdx != 0 {
		t.Errorf("after PrevBookmark: seg=%d, want 0", state.SegmentIdx)
	}
}

func TestToggleBookmark(t *testing.T) {
	df := &datafile.DataFile{
		FilePath: "/test/f1.data",
		Meta:     &datafile.DataMeta{},
		Segments: []*datafile.Segment{
			{Labels: []*datafile.Label{{Species: "Kiwi", Filter: "f"}}},
		},
	}

	state := NewClassifyState(ClassifyConfig{Filter: "f", Reviewer: "Test", Certainty: -1}, []*datafile.DataFile{df})

	if state.HasBookmark() {
		t.Error("should start without bookmark")
	}

	state.ToggleBookmark()
	if !state.HasBookmark() {
		t.Error("should have bookmark after toggle")
	}

	state.ToggleBookmark()
	if state.HasBookmark() {
		t.Error("bookmark should be removed after second toggle")
	}
}

// --- ConfirmLabel ---

func TestConfirmLabel(t *testing.T) {
	df := &datafile.DataFile{
		FilePath: "/test/f1.data",
		Meta:     &datafile.DataMeta{},
		Segments: []*datafile.Segment{
			{Labels: []*datafile.Label{{Species: "Kiwi", Filter: "f", Certainty: 70}}},
		},
	}

	state := NewClassifyState(ClassifyConfig{Filter: "f", Reviewer: "Test", Certainty: -1}, []*datafile.DataFile{df})

	// Confirm should upgrade 70 → 100
	if !state.ConfirmLabel() {
		t.Error("ConfirmLabel should return true for certainty < 100")
	}
	if df.Segments[0].Labels[0].Certainty != 100 {
		t.Errorf("certainty=%d, want 100", df.Segments[0].Labels[0].Certainty)
	}

	// Confirm again on 100 should be no-op
	if state.ConfirmLabel() {
		t.Error("ConfirmLabel should return false when already at 100")
	}
}